diff --git a/package.json b/package.json index fe3d6a75..fcb08556 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,7 @@ "workspaces": [ "packages/*", "packages/web-ui/example", - "packages/coding-agent/examples/extensions/with-deps", - "packages/coding-agent/examples/extensions/pi-dosbox" + "packages/coding-agent/examples/extensions/with-deps" ], "scripts": { "clean": "npm run clean --workspaces", diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/index.ts b/packages/coding-agent/examples/extensions/pi-dosbox/index.ts deleted file mode 100644 index 8e084b9b..00000000 --- a/packages/coding-agent/examples/extensions/pi-dosbox/index.ts +++ /dev/null @@ -1,183 +0,0 @@ -/** - * DOSBox extension for pi - * - * Features: - * - Persistent DOSBox instance running in background - * - QuickBASIC 4.5 mounted at C:\QB - * - /dosbox command to view and interact with DOSBox - * - dosbox tool for agent to send keys, read screen, take screenshots - * - * Usage: pi --extension ./examples/extensions/pi-dosbox - */ - -import { StringEnum } from "@mariozechner/pi-ai"; -import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; -import { Type } from "@sinclair/typebox"; -import { DosboxComponent } from "./src/dosbox-component.js"; -import { DosboxInstance } from "./src/dosbox-instance.js"; - -export default function (pi: ExtensionAPI) { - // Start DOSBox instance at session start - pi.on("session_start", async () => { - try { - await DosboxInstance.getInstance(); - } catch (error) { - console.error("Failed to start DOSBox:", error); - } - }); - - // Clean up on session shutdown - pi.on("session_shutdown", async () => { - await DosboxInstance.destroyInstance(); - }); - - // Register /dosbox command to view DOSBox - pi.registerCommand("dosbox", { - description: "View and interact with DOSBox (Ctrl+Q to detach)", - - handler: async (_args, ctx) => { - if (!ctx.hasUI) { - ctx.ui.notify("DOSBox requires interactive mode", "error"); - return; - } - - // Ensure instance is running - const instance = DosboxInstance.getInstanceSync(); - if (!instance || !instance.isReady()) { - ctx.ui.notify("DOSBox is not running. It should start automatically.", "error"); - return; - } - - await ctx.ui.custom((tui, theme, _kb, done) => { - const fallbackColor = (s: string) => theme.fg("warning", s); - return new DosboxComponent(tui, fallbackColor, () => done(undefined)); - }); - }, - }); - - // Register dosbox tool for agent interaction - pi.registerTool({ - name: "dosbox", - label: "DOSBox", - description: `Interact with DOSBox emulator running QuickBASIC 4.5. -Actions: -- send_keys: Send keystrokes to DOSBox. Use \\n for Enter, \\t for Tab. -- screenshot: Get a PNG screenshot of the current DOSBox screen. -- read_text: Read text-mode screen content (returns null in graphics mode). - -QuickBASIC 4.5 is mounted at C:\\QB. Run "C:\\QB\\QB.EXE" to start it.`, - parameters: Type.Object({ - action: StringEnum(["send_keys", "screenshot", "read_text"] as const, { - description: "The action to perform", - }), - keys: Type.Optional( - Type.String({ - description: - "For send_keys: the keys to send. Use \\n for Enter, \\t for Tab, or special: for special keys (enter, backspace, tab, escape, up, down, left, right, f5)", - }), - ), - }), - - async execute(_toolCallId, params, _onUpdate, _ctx, _signal) { - const { action, keys } = params; - - const instance = DosboxInstance.getInstanceSync(); - if (!instance || !instance.isReady()) { - return { - content: [{ type: "text", text: "Error: DOSBox is not running" }], - details: {}, - }; - } - - switch (action) { - case "send_keys": { - if (!keys) { - return { - content: [{ type: "text", text: "Error: keys parameter required for send_keys action" }], - details: {}, - }; - } - - // Handle special keys - if (keys.startsWith("special:")) { - const specialKey = keys.slice(8) as - | "enter" - | "backspace" - | "tab" - | "escape" - | "up" - | "down" - | "left" - | "right" - | "f5"; - instance.sendSpecialKey(specialKey); - return { - content: [{ type: "text", text: `Sent special key: ${specialKey}` }], - details: {}, - }; - } - - // Handle escape sequences - const processedKeys = keys.replace(/\\n/g, "\n").replace(/\\t/g, "\t").replace(/\\r/g, "\r"); - - instance.sendKeys(processedKeys); - return { - content: [{ type: "text", text: `Sent ${processedKeys.length} characters` }], - details: {}, - }; - } - - case "screenshot": { - const screenshot = instance.getScreenshot(); - if (!screenshot) { - return { - content: [{ type: "text", text: "Error: No frame available yet" }], - details: {}, - }; - } - - return { - content: [ - { - type: "image", - data: screenshot.base64, - mimeType: "image/png", - }, - { - type: "text", - text: `Screenshot: ${screenshot.width}x${screenshot.height} pixels`, - }, - ], - details: {}, - }; - } - - case "read_text": { - const text = instance.readScreenText(); - if (text === null) { - const state = instance.getState(); - return { - content: [ - { - type: "text", - text: `Screen is in graphics mode (${state.width}x${state.height}). Use screenshot action to see the display.`, - }, - ], - details: {}, - }; - } - return { - content: [{ type: "text", text: text || "(empty screen)" }], - details: {}, - }; - } - - default: - return { - content: [{ type: "text", text: `Error: Unknown action: ${action}` }], - details: {}, - }; - } - }, - }); -} diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/package.json b/packages/coding-agent/examples/extensions/pi-dosbox/package.json deleted file mode 100644 index 6f03b3a3..00000000 --- a/packages/coding-agent/examples/extensions/pi-dosbox/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "pi-dosbox", - "private": true, - "version": "0.0.1", - "type": "module", - "scripts": { - "clean": "echo 'nothing to clean'", - "build": "echo 'nothing to build'", - "check": "echo 'nothing to check'" - }, - "pi": { - "extensions": [ - "./index.ts" - ] - }, - "dependencies": { - "emulators": "^8.3.9" - }, - "devDependencies": { - "@types/node": "^20.11.30" - } -} diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/BC.EXE b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/BC.EXE deleted file mode 100644 index 1d1ba2f2..00000000 Binary files a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/BC.EXE and /dev/null differ diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/BCOM45.LIB b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/BCOM45.LIB deleted file mode 100644 index 6b917e5b..00000000 Binary files a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/BCOM45.LIB and /dev/null differ diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/BQLB45.LIB b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/BQLB45.LIB deleted file mode 100644 index 46075967..00000000 Binary files a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/BQLB45.LIB and /dev/null differ diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/BRUN45.LIB b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/BRUN45.LIB deleted file mode 100644 index 3a6b27ca..00000000 Binary files a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/BRUN45.LIB and /dev/null differ diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/LIB.EXE b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/LIB.EXE deleted file mode 100644 index 53ea8d77..00000000 Binary files a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/LIB.EXE and /dev/null differ diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/LINK.EXE b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/LINK.EXE deleted file mode 100644 index 3aa9752f..00000000 Binary files a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/LINK.EXE and /dev/null differ diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB.BI b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB.BI deleted file mode 100644 index b1c9cfd2..00000000 --- a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB.BI +++ /dev/null @@ -1,71 +0,0 @@ -'*** -' QB.BI - Assembly Support Include File -' -' Copyright 1987 Microsoft Corporation -' -' Purpose: -' This include file defines the types and gives the DECLARE -' statements for the assembly language routines ABSOLUTE, -' INTERRUPT, INTERRUPTX, INT86OLD, and INT86XOLD. -' -'*************************************************************************** -' -' Define the type needed for INTERRUPT -' -TYPE RegType - ax AS INTEGER - bx AS INTEGER - cx AS INTEGER - dx AS INTEGER - bp AS INTEGER - si AS INTEGER - di AS INTEGER - flags AS INTEGER -END TYPE -' -' Define the type needed for INTERUPTX -' -TYPE RegTypeX - ax AS INTEGER - bx AS INTEGER - cx AS INTEGER - dx AS INTEGER - bp AS INTEGER - si AS INTEGER - di AS INTEGER - flags AS INTEGER - ds AS INTEGER - es AS INTEGER -END TYPE -' -' DECLARE statements for the 5 routines -' ------------------------------------- -' -' Generate a software interrupt, loading all but the segment registers -' -DECLARE SUB INTERRUPT (intnum AS INTEGER,inreg AS RegType,outreg AS RegType) -' -' Generate a software interrupt, loading all registers -' -DECLARE SUB INTERRUPTX (intnum AS INTEGER,inreg AS RegTypeX, outreg AS RegTypeX) -' -' Call a routine at an absolute address. -' NOTE: If the routine called takes parameters, then they will have to -' be added to this declare statement before the parameter given. -' -DECLARE SUB ABSOLUTE (address AS INTEGER) -' -' Generate a software interrupt, loading all but the segment registers -' (old version) -' -DECLARE SUB INT86OLD (intnum AS INTEGER,_ - inarray(1) AS INTEGER,_ - outarray(1) AS INTEGER) -' -' Gemerate a software interrupt, loading all the registers -' (old version) -' -DECLARE SUB INT86XOLD (intnum AS INTEGER,_ - inarray(1) AS INTEGER,_ - outarray(1) AS INTEGER) -' diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB.EXE b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB.EXE deleted file mode 100644 index 5dd1a1f1..00000000 Binary files a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB.EXE and /dev/null differ diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB.INI b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB.INI deleted file mode 100644 index a348029a..00000000 Binary files a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB.INI and /dev/null differ diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB.LIB b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB.LIB deleted file mode 100644 index 1aea1094..00000000 Binary files a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB.LIB and /dev/null differ diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB.QLB b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB.QLB deleted file mode 100644 index d3ed3fa9..00000000 Binary files a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB.QLB and /dev/null differ diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB45ADVR.OBJ b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB45ADVR.OBJ deleted file mode 100644 index 607d0218..00000000 Binary files a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB45ADVR.OBJ and /dev/null differ diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB45QCK.HLP b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB45QCK.HLP deleted file mode 100644 index 247ddf4e..00000000 Binary files a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB45QCK.HLP and /dev/null differ diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB4UTIL.BI b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB4UTIL.BI deleted file mode 100644 index c95a09f8..00000000 --- a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB4UTIL.BI +++ /dev/null @@ -1,198 +0,0 @@ -' -' QUICKBASIC SUPPORT ROUTINES FOR THEDRAW OBJECT FILES -'----------------------------------------------------------------------------- -' Compatible with Microsoft QuickBasic v4.0 and v4.5 text modes. -'----------------------------------------------------------------------------- -' -' There are a few routines within the QB4UTIL.LIB file. These are -' (along with brief descriptions): -' -' UNCRUNCH - Flash display routine for crunched image files. -' ASCIIDISPLAY - Display routine for ascii only image files. -' NORMALDISPLAY - Display routine for normal full binary image files. -' INITSCREENARRAY - Maps a dynamic integer array to the physical video -' memory. -' -'============================================================================= -' UNCRUNCH (imagedata,video offset) -' ASCIIDISPLAY (imagedata,video offset) -' NORMALDISPLAY (imagedata,video offset) -'============================================================================= -' -' These three subroutines operate similarly. Each takes a specific data -' format (TheDraw crunched data, ascii only, or normal binary) and displays -' the image on the screen. Monochrome and color text video displays are -' supported. The integer offset parameter is useful with block images, -' giving control over where the block appears. -' -' Example calls: -' CALL UNCRUNCH (ImageData&,vidoffset%) <- for crunched data -' CALL ASCIIDISPLAY (ImageData&,vidoffset%) <- for ascii-only data -' CALL NORMALDISPLAY (ImageData&,vidoffset%) <- for normal binary data -' -' The parameter IMAGEDATA is the identifier you assign when saving -' a QuickBasic object file with TheDraw. ImageData actually becomes a -' short function returning information Uncrunch, AsciiDisplay, and -' NormalDisplay use to find the screen contents. In addition, three -' other related integer functions are created. Assuming the identifier -' IMAGEDATA, these are: -' -' IMAGEDATAWIDTH% -' IMAGEDATADEPTH% -' IMAGEDATALENGTH% -' -' The width and depth functions return the size of the block in final -' form (ie: a full screen would yield the numbers 80 and 25 respectfully). -' The length function returns the size of the stored data. For crunched -' files and block saves this might be very small. For a 80x25 full screen -' binary image it will be 4000 bytes. The integer functions are useful for -' computing screen or window dimensions, etc... -' -' You must declare all four functions in your Basic source code before -' they can be used (naturally). The following code example illustrates. -' The identifier used is IMAGEDATA. The data is a 40 character by 10 line -' block saved as normal binary. -' -' ---------------------------------------------------------------------- -' REM $INCLUDE: 'QB4UTIL.BI' -' DECLARE FUNCTION ImageData& ' Important! Do not neglect -' DECLARE FUNCTION ImageDataWidth% ' the "&" and "%" symbols -' DECLARE FUNCTION ImageDataDepth% ' after the function names. -' DECLARE FUNCTION ImageDataLength% -' -' CALL NORMALDISPLAY (ImageData&, 34 *2+( 5 *160)-162) -' ---------------------------------------------------------------------- -' -' That's it! The above displays the 40x10 block at screen coordinates -' column 34, line 5 (note these two numbers in above example). If the -' data was crunched or ascii use the corresponding routine. -' -' Note: The ascii-only screen image does not have any color controls. -' Whatever the on-screen colors were before, they will be after. -' You might want to insert COLOR and CLS statements before calling -' the ASCIIDISPLAY routine. -' -' Regardless of which routine used, each remembers the original horizontal -' starting column when it goes to the next line. This permits a block to -' be displayed correctly anywhere on the screen. ie: -' -' +-------------------------------------------------+ -' | | -' | | <- Pretend this -' | | is the video -' | ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ | display. -' | ³ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ³ | -' | ³ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ³ | -' | ³ÛÛ ImageData block ÛÛ³ | -' | ³ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ³ | -' | ³ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ³ | -' | ³ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ³ | -' | ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ | -' | | -' | | -' | | -' +-------------------------------------------------+ -' -' -' The ImageData block could be shown in the upper-left corner of the -' screen by changing the call to: -' -' CALL NORMALDISPLAY (ImageData&,0) -' -' Notice the video offset has been removed, since we want the upper-left -' corner. To display the block in the lower-right corner you would use: -' -' CALL NORMALDISPLAY (ImageData&, 40 *2+( 15 *160)-162) -' -' The block is 40 characters wide by 10 lines deep. Therefore to display -' such a large block, we must display the block at column 40, line 15. -' (column 80 minus 40, line 25 minus 10). -' -' -' NOTES ON THE UNCRUNCH ROUTINE -' -------------------------------------------------------------------------- -' -' Many people favor "crunching" screens with TheDraw because the size -' of the data generally goes down. When uncrunching an image however, -' there is no guarantee what was previously on-screen will be replaced. -' -' In particular, the uncruncher assumes the screen is previously erased to -' black thus permitting better data compression. For instance, assume the -' video completely filled with blocks, overwritten by an uncrunched image: -' -' ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ -' ³ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ³ ³tetetetetetÛÛÛÛÛÛÛÛÛÛ³ -' ³ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ³ ³ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ³ -' ³ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ³ ³ eteteteteteteÛÛÛÛ³ -' ³ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ³ ³tetetetetÛÛÛÛÛÛÛÛÛÛÛÛ³ -' ³ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ³ ³ eteÛÛÛÛÛÛÛÛÛ³ -' ³ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ³ ³ etetetetetetÛÛ³ -' ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ -' before uncrunch after uncrunch -' -' By omitting a CLS statement, the new text appears surrounded by bits of -' the previous screen. Proper usage would typically be: -' -' ---------------------------------------------------------------------- -' REM $INCLUDE: 'QB4UTIL.BI' -' DECLARE FUNCTION ImageData& ' Important! Do not neglect -' DECLARE FUNCTION ImageDataWidth% ' the "&" and "%" symbols -' DECLARE FUNCTION ImageDataDepth% ' after the function names. -' DECLARE FUNCTION ImageDataLength% -' -' COLOR 15,0 : CLS ' Clear to black screen -' CALL UNCRUNCH (ImageData&, 34 *2+( 5 *160)-162) -' ---------------------------------------------------------------------- -' -' -'============================================================================= -' INITSCREENARRAY -'============================================================================= -' -' To directly access the video screen memory requires you to use the -' PEEK/POKE statements after setting the DEF SEG value. A cumbersome -' and compiler inefficient approach. In addition, you must have some -' way of determining if a monochrome or color video is being used before -' the DEF SEG can be set properly. -' -' This subroutine offers a simpler approach, by effectively mapping or -' placing an integer array over the video screen. Instead of PEEK/POKE, -' you merely reference an array element. ie: -' -' ---------------------------------------------------------------------- -' REM $INCLUDE: 'QB4UTIL.BI' -' -' REM $DYNAMIC <- very important to place this before DIM statement -' DIM S%(0) -' CALL INITSCREENARRAY (S%()) -' -' S%(0) = ASC("H") + 15 *256 + 1 *4096 -' S%(1) = ASC("E") + 15 *256 + 1 *4096 -' S%(2) = ASC("L") + 15 *256 + 1 *4096 -' S%(3) = ASC("L") + 15 *256 + 1 *4096 -' S%(4) = ASC("O") + 15 *256 + 1 *4096 -' ---------------------------------------------------------------------- -' -' The above example directly places the message "HELLO" on the screen -' for you, in white lettering (the 15*256) on a blue background (1*4096). -' To alter the foreground color, change the 15's to some other number. -' Change the 1's for the background color. -' -' Each array element contains both the character to display plus the -' color information. This explains the bit of math following each -' ASC statement. You could minimize this using a FOR/NEXT loop. -' -' The S% array has 2000 elements (0 to 1999) representing the entire -' 80 by 25 line video. If in an EGA/VGA screen mode change the 1999 to -' 3439 or 3999 respectfully. -' -' There is no pressing reason to use the array approach, however it -' does free up the DEFSEG/PEEK/POKE combination for other uses. In -' any case, enjoy! -' -' -DECLARE SUB UNCRUNCH (X&, Z%) -DECLARE SUB ASCIIDISPLAY (X&, Z%) -DECLARE SUB NORMALDISPLAY (X&, Z%) -DECLARE SUB INITSCREENARRAY (A%()) - \ No newline at end of file diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB4UTIL.LIB b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB4UTIL.LIB deleted file mode 100644 index 7565fdfe..00000000 Binary files a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB4UTIL.LIB and /dev/null differ diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB_EDR.LIB b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB_EDR.LIB deleted file mode 100644 index 6199c0d6..00000000 Binary files a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/QB_EDR.LIB and /dev/null differ diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/README.DOC b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/README.DOC deleted file mode 100644 index 96e8ccd4..00000000 --- a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/README.DOC +++ /dev/null @@ -1,545 +0,0 @@ - README.DOC File - - Release Notes for Microsoft (R) QuickBASIC - - Version 4.50 - - (C) Copyright Microsoft Corporation, 1990 - - Product Serial Number: 00-007-1450-26147102 - - -This document contains release notes for version 4.50 of the Microsoft (R) -QuickBASIC for MS-DOS (R). The information in this document is more -up-to-date than that in the manuals. - - -================================================================================ -Contents -================================================================================ - - -Part Description ----- ----------- - -1 Using QuickBASIC on a Two-Floppy System - -2 Using Your Mouse with QuickBASIC - -3 Supplementary Information on Mixed-Language Programming - -4 Using Btrieve with QuickBASIC - -5 Using the DOS 3.2 Patch for Math Accuracy - -6 Miscellaneous Information About Using QuickBASIC - - -================================================================================ -Part 1: Using QuickBASIC on a Two-Floppy System -================================================================================ - - -Installing QuickBASIC on Floppy Disks -------------------------------------- - -The SETUP program can install QuickBASIC on floppy disks for use -with a two-floppy system. You must run SETUP to install QuickBASIC -on floppy disks. You cannot run QuickBASIC from the disks provided, -because the files are stored in a compressed format. - -Before you install QuickBASIC on your two-floppy system, be sure -you have enough blank, formatted disks. If you have 360K disk -drives, you will need five blank disks. For 720K disk drives, you -will need three blank disks. - -To install QuickBASIC, put Disk #1 in drive A. Type A:\SETUP and -press Enter. - -When your installation is complete, you should label each disk with -the names of the files that are on that disk. QuickBASIC will ask -you to swap disks when it cannot find a file that it needs, and -you will need to know which disk the file is on. - -If you use 360K disks, label them as follows: - -PROGRAM: -QB.EXE QB45QCK.HLP - -UTILITIES: -BC.EXE LINK.EXE -BQLB45.LIB LIB.EXE -BRUN45.EXE QB.QLB -BRUN45.LIB QB.LIB - -UTILITIES 2: -BCOM45.LIB QB45ENER.HLP - -ADVISOR: -QB45ADVR.HLP - -EXAMPLES -QB.BI BASIC examples - -If you use 720K disks, label them as follows: - -PROGRAM/EXAMPLES: -QB.EXE QB45QCK.HLP -QB.BI BASIC examples - -UTILITIES: -BC.EXE LINK.EXE -BQLB45.LIB LIB.EXE -BRUN45.EXE QB.QLB -BRUN45.LIB QB.LIB -BCOM45.LIB - -ADVISOR: -QB45ADVR.HLP QB45ENER.HLP - - -Running QuickBASIC from Floppy Disks ------------------------------------- - -During some operations, QuickBASIC asks you to swap disks one -or more times. You can minimize disk swapping by following the -procedures in this section. - -Since the disks that you installed QuickBASIC on are nearly full, you -should keep your BASIC source-code (.BAS) files on a separate disk. -Label this disk SOURCE. - -Copy the run-time module BRUN45.EXE from the UTILITIES disk to your -SOURCE disk. QuickBASIC needs this file to run executable programs -compiled with the run-time support option. - -When you use QuickBASIC, a disk containing source-code (.BAS) files -should always be in drive B. If you want to run existing BASIC -programs (such as the example programs provided with QuickBASIC), -remove the SOURCE disk from drive B and insert the disk containing -these files. - -To run QuickBASIC: - -1. Insert the SOURCE disk in drive B. - -2. To make drive B the current drive, type B: and press Enter. - -3. Insert the PROGRAM disk (the disk containing QB.EXE) in drive A. - -4. Type the following command: - -A:QB.EXE - -To insure that QuickBASIC always looks on both disk drives for the -files it needs, follow these steps: - -1. From the Options menu, choose Set Paths. - -2. Make sure each of the path settings includes both disk drives. The -following line should be in all four text boxes: - -A:\;B:\ - -3. Choose OK. - -QuickBASIC saves these path settings in the QB.INI file, so you will -not have to enter them again. - -When you exit QuickBASIC or shell to DOS, you will be prompted to -insert a disk containing the file COMMAND.COM. Remove the PROGRAM -disk from drive A, insert a system disk, and press Enter. - - -Using Help from Floppy Disks ----------------------------- - -When you use the QuickBASIC Advisor online help system, you may need -to swap disks. For example, if you choose "Details" or "Example" on a -help screen, QuickBASIC will inform you that it cannot find the help -file QB45ADVR.HLP. Put the disk that contains this file in drive A and -choose Retry. - - -Compiling Your Programs from Floppy Disks ------------------------------------------ - -To compile your program from within QuickBASIC: - -1. From the Run menu, choose Make EXE File. - -2. Choose Make EXE. QuickBASIC displays the message "Cannot find file -(BC.EXE)." - -3. Insert the UTILITIES disk (the disk containing BC.EXE) in drive A. -Type A: and press Enter. - -If the program compiles successfully, QuickBASIC invokes the LINK -utility. If LINK cannot find the library, it displays the following -message: - -LINK : warning L4051 : BCOM45.LIB : cannot find library -Enter new file spec: - -4. Insert the disk containing the requested library (BCOM45.LIB or -BRUN45.LIB) in drive A. - -Note: The requested library may be located on the UTILITIES disk -already in drive A. If this is the case, leave this disk in drive A. - -5. Type A: and press Enter. After the LINK utility finishes creating -your executable program, QuickBASIC displays the message "Cannot -find file (QB.EXE)." - -6. Insert the PROGRAM disk in drive A. - -7. Type A: and press Enter. - -If no errors occur during compiling or linking, your compiled program -(.EXE) is created on drive B. QuickBASIC also creates an object-module -(.OBJ) file. To save space, you can delete object-module files. - - -================================================================================ -Part 2: Using Your Mouse with QuickBASIC -================================================================================ - - -New Mouse Driver for Use with QuickBASIC ----------------------------------------- - -QuickBASIC Version 4.5 can be used with any mouse that is 100% -compatible with the Microsoft Mouse. However, you must use a -Microsoft Mouse driver Version 6.00 or later. Earlier versions may -cause unpredictable behavior when used with QuickBASIC. MOUSE.COM, -Version 6.24 is supplied with QuickBASIC Version 4.5. - -Especially if you are writing programs that use the mouse, you -should use the supplied version of the mouse driver when working in -QuickBASIC. Previous versions have included MOUSE.SYS, which is -installed by including the line DEVICE=MOUSE.SYS in your CONFIG.SYS -file. This version of QuickBASIC includes MOUSE.COM, which is not -installed via CONFIG.SYS. To install MOUSE.COM, just type MOUSE at -the DOS prompt. To include MOUSE.COM automatically when your machine -boots, make sure MOUSE.COM is in your search path, then put the line - -MOUSE - -in your AUTOEXEC.BAT file. To free up memory, you can remove the -mouse driver at any time by typing MOUSE OFF at the DOS prompt. -This will restore between 9K and 10.5K of memory with Version 6.11. - - -Using Mouse Function Calls from QuickBASIC Programs ---------------------------------------------------- - -If you are programming for the Microsoft Mouse, you should obtain -the Microsoft Mouse Programmer's Reference Guide and the library -MOUSE.LIB that comes with it. (These are not included in QuickBASIC -or Mouse package and must be ordered separately). Most of the -information in the Mouse Programmer's Reference Guide applies -directly to QuickBASIC Version 4.5. However, the following additional -restrictions must be observed: - -Certain Mouse function calls (Functions 9 & 16) require you to set -up an integer array and pass the address of the array to the mouse -driver. For previous versions, the only restriction on this array -was that it had to be $STATIC (the default array type). In QuickBASIC -Version 4.5, however, the array also must be in a COMMON block if you -will be making the Mouse function call from within the QuickBASIC -environment. In addition, it is recommended that the support code -for the Mouse call be in a Quick library or linked into the -executable file when making Mouse function calls from QuickBASIC. - -To produce a Quick library for using Mouse function calls from -within the QuickBASIC environment, use the following command line -(produces MOUSE.QLB): - -LINK MOUSE.LIB/QU,MOUSE.QLB,,BQLB40.LIB/NOE; - -An example from PIANO.BAS (included with the Microsoft Mouse -Programmer's Reference) for using Mouse function call 9: - -DEFINT A-Z -DECLARE SUB MOUSE (M1, M2, M3, M4) -DIM Cursor(15, 1) -COMMON Cursor() 'Ensures array data is in DGROUP -. -. (set up Cursor() for mouse cursor shape desired) -. -M1 = 9: M2 = 6: M3 = 0 -CALL MOUSE(M1, M2, M3, VARPTR(Cursor(0, 0))) - -In addition to the above, note that Mouse function calls 21-23 -require dynamically allocated storage out of the home data segment. -The recommended way to do this is to allocate space in a dynamic -string variable based on the return value from function call 21, -using the STRING$ or SPACE$ function. Then use VARPTR on this string -variable just prior to calling Mouse function call 22 or 23. - - -================================================================================ -Part 3: Supplementary Information on Mixed-Language Programming -================================================================================ - - -Linking from Within QuickC or with QCL --------------------------------------- - -Microsoft QuickC and the QCL command both set the /NOI linker -by default. Therefore, you should not link from within QuickC, or -with QCL, when your program contains modules written in a case- -insensitive language such as BASIC. Use LINK to link your program -from the command line. - - -Pascal and FORTRAN Modules in QuickBASIC Programs -------------------------------------------------- - -Modules compiled with Microsoft Pascal or FORTRAN can be linked with -BASIC programs, as described in the Microsoft Mixed-Language -Programming Guide. They can also be incorporated in Quick libraries. -However, QuickBASIC programs containing code compiled with Microsoft -Pascal must allocate at least 2K near-heap space for Pascal. This can -be done by using the DIM statement to allocate a static array of 2K or -greater in the NMALLOC named common block, for example, as follows: - -DIM name%(2048) -COMMON SHARED /NMALLOC/name%() - -The Pascal run-time assumes it always has at least 2K of near-heap -space available. If the Pascal code cannot allocate the required -space, QuickBASIC may crash. This applies to Pascal code in Quick -libraries as well as Pascal code linked into executable files. The -situation is similar for FORTRAN I/O, which also requires near -buffer space, and which can be provided by the same means as the -Pascal near malloc space. - - -STATIC Array Allocation ------------------------ - -If you are writing assembly-language modules for use in QuickBASIC -programs, see Section 2.3.3, "Variable Storage Allocation," in the -BASIC Language Reference. Assembly-language code should not assume -data is in a particular segment. To avoid problems, pass data using -the SEG or CALLS keywords, or use FAR pointers. Alternatively, you -can declare all arrays dynamic (still using far pointers) since -dynamic arrays are handled identically by BC and within QuickBASIC. - - -Quick Libraries with Leading Zeros in the First Code Segment ------------------------------------------------------------- - -A Quick library containing leading zeros in the first CODE segment -is invalid, causing the message "Error in loading file - -Invalid format" when you try to load it in QuickBASIC. For example, -this can occur if an assembly-language routine puts data that is -initialized to zero in the first CODE segment, and it is subsequently -listed first on the LINK command line when you make a Quick library. -If you have this problem, do either of the following: -(1) link with a BASIC module first on the LINK command line, or -(2) make sure that, in whatever module comes first on the LINK -command line, the first code segment starts with a non-zero byte. - - -References to DGROUP in Extended Run-Time Modules -------------------------------------------------- - -For mixed-language programs that use the CHAIN command, you should -make sure that any code built into an extended run-time module does not -contain any references to DGROUP. (The CHAIN command causes DGROUP to -move, but does not update references to DGROUP.) This rule applies -only to mixed-language programs; because BASIC routines never refer -to DGROUP, you can ignore this caution for programs written entirely -in BASIC. - -To avoid this problem, you can use the value of SS, since BASIC always -assumes that SS coincides with DGROUP. - - -================================================================================ -Part 4: Using Btrieve -================================================================================ - - -Using Btrieve in OS/2 Protected Mode ------------------------------------- - -In OS/2 protected mode, a BASIC program that uses Btrieve must do a -Btrieve reset call (function 28) before executing the CHAIN statement. -The program must also reopen all Btrieve files when the destination of -the CHAIN starts to run. - - -Using Btrieve with QuickBASIC ------------------------------ - -If you use Btrieve with QuickBASIC, you must make a small change to -your programs for QuickBASIC Version 4.5. Currently your programs -contain a statement that obtains the address of the field buffer for -an open file. For example: - -OPEN "NUL" AS #1 -FIELD #1, 20 AS CITY$, 10 AS STATE$ -FCB.ADDR% = VARPTR(#1) 'This statement obtains the address - -In QuickBASIC Version 4.5, you should change the indicated statement -to return the address of the first variable in your field buffer minus -a constant, as follows: - -OPEN "NUL" AS #1 -FIELD #1, 20 AS CITY$, 10 AS STATE$ -FCB.ADDR% = SADD(CITY$) - 188 ' CITY$ is the first field - ' buffer variable - -The following example shows how to obtain the same address for a -user-defined type: - -TYPE ADDRESS -CITY AS STRING * 20 -STATE AS STRING * 10 -END TYPE - -DIM ADD1 AS ADDRESS - -FCB.ADDR% = VARPTR(ADD1) - 188 -' or, you can use FCB.ADDR% = VARPTR(ADD1.CITY) - 188 - -Your programs should function correctly with Btrieve with this change. - - -================================================================================ -Part 5: DOS 3.20 Patch -================================================================================ - -This information is important only if your system has all of the -following characteristics: - -1. Uses MS-DOS version 3.20 -2. Boots from a hard disk drive -3. Has a math coprocessor (for instance, an 8087 chip) -4. Runs programs that use floating-point math - -For systems that satisfy all of the preceding conditions, you may be -able to eliminate floating-point math problems by installing a small -patch in DOS. If you are not sure whether you need the patch, perform -the following steps: - -1. Copy the program PATCH87.EXE (included in this release) to the root -directory of your hard-disk drive. - -2. Reboot your system from the hard disk, and do not perform any floppy- -disk operations after rebooting. It is very important that you avoid -floppy-disk I/O after rebooting, since that will affect the -reliability of the diagnostic test that you are about to perform. - -3. If necessary, use the CD command to move to the root directory of -your hard-disk drive. - -4. Run the PATCH87.EXE program by entering this command at the DOS -prompt: - -PATCH87 - -5. The program performs a diagnostic test on your system to determine -whether it needs the DOS patch, and if the patch is needed, -whether the patch can be installed successfully. If the program -tells you that you need to install the DOS patch, and that it can be -done, follow the procedure described in the next section. - -Note: The floating-point problem has been eliminated in versions of -MS-DOS higher than 3.20. This includes MS-DOS versions 3.21 and 3.30. - -If you performed the preceding test and determined that you should -install the DOS patch on your system, perform the following steps: - -1. Format a blank floppy disk. (Do NOT use the /s formatting option to -transfer system files to the disk.) - -2. Use the SYS command to copy IO.SYS and MSDOS.SYS from the root -directory of your hard disk to the new floppy disk. For instance, if -you boot from drive C:, you would enter the following commands: - -C: -SYS A: - -3. Use the COPY command to copy COMMAND.COM and SYS.COM to the same -floppy disk. - -4. Use the COPY command to copy the program PATCH87.EXE (included in -this release) to the same floppy disk. - -5. Change the current drive and directory to the floppy disk, by -entering the following command: - -A: - -7. Install the DOS patch by entering the following command: - -PATCH87 /F - -WARNING: If you experience any disk errors during steps 2 through 7, -do not proceed with step 8. Reboot from your hard disk and repeat the -entire process. - -8. If you have not experienced any errors, use the SYS command to -transfer the files IO.SYS and MSDOS.SYS from the floppy disk back to -your hard disk. For instance, if the boot directory of your system -is the root directory of drive C:, you would enter the following -command at the DOS prompt: - -A: -SYS C: - -9. The DOS patch has been installed. Reboot the system. - - -================================================================================ -Part 6: Miscellaneous Information About Using QuickBASIC -================================================================================ - - -Using FIXSHIFT.COM Utility --------------------------- - -Some keyboards have an extra set of DIRECTION (i.e. arrow) keys, in -addition to those on the numeric keypad. A bug in the ROM BIOS of -some machines with these keyboards can interfere with the QuickBASIC -editor. The Utilities 2 disk includes a program, FIXSHIFT.COM, that -fixes this bug. If you have such a keyboard, run this program by typing -FIXSHIFT. If your machine does not have the bug, FIXSHIFT displays a -message telling you so. Otherwise FIXSHIFT prompts you for the proper -actions. FIXSHIFT takes about 450 bytes of memory. Except for the BIOS -bug, it has no effect on other programs you run. - - -Note on VGA Display Adapters ----------------------------- - -If you install an IBM (R) Personal System/2 (TM) Video Graphics -Array display adapter (VGA) in a non-PS/2 machine, the VGA adapter -should be the only adapter in the system, and you should not use -monochrome modes (SCREEN 10) if you have a color monitor. Similarly, -you should not use color modes (SCREEN 1, 2, 7, 8, 9, 11, 12, 13) if -you have a monochrome monitor. - - -Note on Using QuickBASIC with DOS 2.1 -------------------------------------- - -To use QuickBASIC with a two-floppy system under DOS 2.1, you must -put a copy of COMMAND.COM on each disk containing an executable -file ( a file with the .EXE extension). - - -PTR86, LOF, Naming SUB Procedures and Variables ------------------------------------------------ - -PTR86 is no longer supported. Use VARSEG and VARPTR instead. -Also, when used with a communications device, LOF now returns the -amount of space remaining (in bytes) in the output buffer. In -previous versions this was returned in the input buffer. Also, note -that a variable and SUB procedure could have the same name in -previous versions. In Version 4.5, this causes a "Duplicate -definition" error message. diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/Readme!!!!!!.txt b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/Readme!!!!!!.txt deleted file mode 100644 index 54ca62a1..00000000 --- a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/Readme!!!!!!.txt +++ /dev/null @@ -1,6 +0,0 @@ -Please check "Use folder names." when you extract files (Using Winzip) or use /d option with PKZip because all the path in the INI files are set to C:\QB directory. -If you extract it in a different directory, set new paths in -Help>Set Paths from the Basic menu. - -<-- Other Basic versions and many other compilers and files are available -at http://members.xoom.com/qb_best/ --> \ No newline at end of file diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/qb45advr.hlp b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/qb45advr.hlp deleted file mode 100644 index 7d602e9e..00000000 Binary files a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/qb45advr.hlp and /dev/null differ diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/qb45ener.hlp b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/qb45ener.hlp deleted file mode 100644 index ea9ae950..00000000 Binary files a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/qb45ener.hlp and /dev/null differ diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/start.bat b/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/start.bat deleted file mode 100644 index c57bb5a6..00000000 --- a/packages/coding-agent/examples/extensions/pi-dosbox/qbasic/start.bat +++ /dev/null @@ -1 +0,0 @@ -qb/l \ No newline at end of file diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/src/dosbox-component.ts b/packages/coding-agent/examples/extensions/pi-dosbox/src/dosbox-component.ts deleted file mode 100644 index 995c7249..00000000 --- a/packages/coding-agent/examples/extensions/pi-dosbox/src/dosbox-component.ts +++ /dev/null @@ -1,425 +0,0 @@ -/** - * DOSBox TUI Component - * - * Renders DOSBox framebuffer as an image in the terminal. - * Connects to the persistent DosboxInstance. - */ - -import type { Component } from "@mariozechner/pi-tui"; -import { - allocateImageId, - deleteKittyImage, - Image, - type ImageTheme, - isKeyRelease, - Key, - matchesKey, - truncateToWidth, -} from "@mariozechner/pi-tui"; -import { DosboxInstance } from "./dosbox-instance.js"; - -const MAX_WIDTH_CELLS = 120; - -// js-dos key codes -const KBD: Record = { - enter: 257, - backspace: 259, - tab: 258, - esc: 256, - space: 32, - leftshift: 340, - rightshift: 344, - leftctrl: 341, - rightctrl: 345, - leftalt: 342, - rightalt: 346, - up: 265, - down: 264, - left: 263, - right: 262, - home: 268, - end: 269, - pageup: 266, - pagedown: 267, - insert: 260, - delete: 261, - f1: 290, - f2: 291, - f3: 292, - f4: 293, - f5: 294, - f6: 295, - f7: 296, - f8: 297, - f9: 298, - f10: 299, - f11: 300, - f12: 301, -}; - -export class DosboxComponent implements Component { - private tui: { requestRender: () => void }; - private onClose: () => void; - private instance: DosboxInstance | null = null; - private image: Image | null = null; - private imageTheme: ImageTheme; - private loadingMessage = "Connecting to DOSBox..."; - private errorMessage: string | null = null; - private cachedLines: string[] = []; - private cachedWidth = 0; - private cachedVersion = -1; - private version = 0; - private disposed = false; - private imageId: number; - private kittyPushed = false; - private frameListener: ((rgba: Uint8Array, width: number, height: number) => void) | null = null; - - wantsKeyRelease = true; - - constructor(tui: { requestRender: () => void }, fallbackColor: (s: string) => string, onClose: () => void) { - this.tui = tui; - this.onClose = onClose; - this.imageTheme = { fallbackColor }; - this.imageId = allocateImageId(); - void this.connect(); - } - - private async connect(): Promise { - try { - this.instance = await DosboxInstance.getInstance(); - - // Set up frame listener - this.frameListener = (rgba: Uint8Array, width: number, height: number) => { - this.updateFrame(rgba, width, height); - }; - this.instance.addFrameListener(this.frameListener); - - // Get initial state - const state = this.instance.getState(); - if (state.lastFrame && state.width && state.height) { - this.updateFrame(state.lastFrame, state.width, state.height); - } - - // Push Kitty enhanced mode for proper key press/release - process.stdout.write("\x1b[>15u"); - this.kittyPushed = true; - - this.tui.requestRender(); - } catch (error) { - this.errorMessage = error instanceof Error ? error.message : String(error); - this.tui.requestRender(); - } - } - - private updateFrame(rgba: Uint8Array, width: number, height: number): void { - const png = this.encodePng(width, height, rgba); - const base64 = png.toString("base64"); - this.image = new Image( - base64, - "image/png", - this.imageTheme, - { maxWidthCells: MAX_WIDTH_CELLS, imageId: this.imageId }, - { widthPx: width, heightPx: height }, - ); - this.version++; - this.tui.requestRender(); - } - - private encodePng(width: number, height: number, rgba: Uint8Array): Buffer { - const { deflateSync } = require("node:zlib"); - const stride = width * 4; - const raw = Buffer.alloc((stride + 1) * height); - for (let y = 0; y < height; y++) { - const rowOffset = y * (stride + 1); - raw[rowOffset] = 0; - raw.set(rgba.subarray(y * stride, y * stride + stride), rowOffset + 1); - } - - const compressed = deflateSync(raw); - - const header = Buffer.alloc(13); - header.writeUInt32BE(width, 0); - header.writeUInt32BE(height, 4); - header[8] = 8; - header[9] = 6; - - const signature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); - const ihdr = this.createChunk("IHDR", header); - const idat = this.createChunk("IDAT", compressed); - const iend = this.createChunk("IEND", Buffer.alloc(0)); - - return Buffer.concat([signature, ihdr, idat, iend]); - } - - private createChunk(type: string, data: Buffer): Buffer { - const length = Buffer.alloc(4); - length.writeUInt32BE(data.length, 0); - const typeBuffer = Buffer.from(type, "ascii"); - const crcBuffer = Buffer.concat([typeBuffer, data]); - const crc = this.crc32(crcBuffer); - const crcOut = Buffer.alloc(4); - crcOut.writeUInt32BE(crc, 0); - return Buffer.concat([length, typeBuffer, data, crcOut]); - } - - private crc32(buffer: Buffer): number { - let crc = 0xffffffff; - for (const byte of buffer) { - crc = CRC_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8); - } - return (crc ^ 0xffffffff) >>> 0; - } - - handleInput(data: string): void { - const released = isKeyRelease(data); - - if (!released && matchesKey(data, Key.ctrl("q"))) { - this.dispose(); - this.onClose(); - return; - } - - const ci = this.instance?.getCommandInterface(); - if (!ci) return; - - const parsed = parseKeyWithModifiers(data); - if (!parsed) return; - - const { keyCode, shift, ctrl, alt } = parsed; - - if (shift) ci.sendKeyEvent(KBD.leftshift, !released); - if (ctrl) ci.sendKeyEvent(KBD.leftctrl, !released); - if (alt) ci.sendKeyEvent(KBD.leftalt, !released); - - ci.sendKeyEvent(keyCode, !released); - } - - invalidate(): void { - this.cachedWidth = 0; - } - - render(width: number): string[] { - if (this.errorMessage) { - return [truncateToWidth(`DOSBox error: ${this.errorMessage}`, width)]; - } - if (!this.instance?.isReady()) { - return [truncateToWidth(this.loadingMessage, width)]; - } - if (!this.image) { - return [truncateToWidth("Waiting for DOSBox frame...", width)]; - } - if (width === this.cachedWidth && this.cachedVersion === this.version) { - return this.cachedLines; - } - - const imageLines = this.image.render(width); - const footer = truncateToWidth("\x1b[2mCtrl+Q to detach (DOSBox keeps running)\x1b[22m", width); - const lines = [...imageLines, footer]; - - this.cachedLines = lines; - this.cachedWidth = width; - this.cachedVersion = this.version; - - return lines; - } - - dispose(): void { - if (this.disposed) return; - this.disposed = true; - - // Delete the terminal image - process.stdout.write(deleteKittyImage(this.imageId)); - - if (this.kittyPushed) { - process.stdout.write("\x1b[>> 1); - } else { - c >>>= 1; - } - } - table[i] = c >>> 0; - } - return table; -} - -interface ParsedKey { - keyCode: number; - shift: boolean; - ctrl: boolean; - alt: boolean; -} - -function decodeModifiers(modifierField: number): { shift: boolean; ctrl: boolean; alt: boolean } { - const modifiers = modifierField - 1; - return { - shift: (modifiers & 1) !== 0, - alt: (modifiers & 2) !== 0, - ctrl: (modifiers & 4) !== 0, - }; -} - -function parseKeyWithModifiers(data: string): ParsedKey | null { - if (data.startsWith("\x1b[") && data.endsWith("u")) { - const body = data.slice(2, -1); - const [keyPart, modifierPart] = body.split(";"); - if (keyPart) { - const codepoint = parseInt(keyPart.split(":")[0], 10); - if (!Number.isNaN(codepoint)) { - const modifierField = modifierPart ? parseInt(modifierPart.split(":")[0], 10) : 1; - const { shift, alt, ctrl } = decodeModifiers(Number.isNaN(modifierField) ? 1 : modifierField); - const keyCode = codepointToJsDosKey(codepoint); - if (keyCode !== null) { - return { keyCode, shift, ctrl, alt }; - } - } - } - } - - const csiMatch = data.match(/^\x1b\[(\d+);(\d+)(?::\d+)?([~A-Za-z])$/); - if (csiMatch) { - const code = parseInt(csiMatch[1], 10); - const modifierField = parseInt(csiMatch[2], 10); - const suffix = csiMatch[3]; - const { shift, alt, ctrl } = decodeModifiers(modifierField); - const keyCode = mapCsiKeyToJsDos(code, suffix); - if (keyCode === null) return null; - return { keyCode, shift, ctrl, alt }; - } - - const keyCode = mapKeyToJsDos(data); - if (keyCode === null) return null; - const shift = data.length === 1 && data >= "A" && data <= "Z"; - return { keyCode, shift, ctrl: false, alt: false }; -} - -function codepointToJsDosKey(codepoint: number): number | null { - if (codepoint === 13) return KBD.enter; - if (codepoint === 9) return KBD.tab; - if (codepoint === 27) return KBD.esc; - if (codepoint === 8 || codepoint === 127) return KBD.backspace; - if (codepoint === 32) return KBD.space; - if (codepoint >= 97 && codepoint <= 122) return codepoint - 32; - if (codepoint >= 65 && codepoint <= 90) return codepoint; - if (codepoint >= 48 && codepoint <= 57) return codepoint; - return null; -} - -function mapCsiKeyToJsDos(code: number, suffix: string): number | null { - switch (suffix) { - case "A": - return KBD.up; - case "B": - return KBD.down; - case "C": - return KBD.right; - case "D": - return KBD.left; - case "H": - return KBD.home; - case "F": - return KBD.end; - case "P": - return KBD.f1; - case "Q": - return KBD.f2; - case "R": - return KBD.f3; - case "S": - return KBD.f4; - case "Z": - return KBD.tab; - case "~": - switch (code) { - case 1: - case 7: - return KBD.home; - case 2: - return KBD.insert; - case 3: - return KBD.delete; - case 4: - case 8: - return KBD.end; - case 5: - return KBD.pageup; - case 6: - return KBD.pagedown; - case 15: - return KBD.f5; - case 17: - return KBD.f6; - case 18: - return KBD.f7; - case 19: - return KBD.f8; - case 20: - return KBD.f9; - case 21: - return KBD.f10; - case 23: - return KBD.f11; - case 24: - return KBD.f12; - default: - return null; - } - default: - return null; - } -} - -function mapKeyToJsDos(data: string): number | null { - if (matchesKey(data, Key.enter)) return KBD.enter; - if (matchesKey(data, Key.backspace)) return KBD.backspace; - if (matchesKey(data, Key.tab)) return KBD.tab; - if (matchesKey(data, Key.escape)) return KBD.esc; - if (matchesKey(data, Key.space)) return KBD.space; - if (matchesKey(data, Key.up)) return KBD.up; - if (matchesKey(data, Key.down)) return KBD.down; - if (matchesKey(data, Key.left)) return KBD.left; - if (matchesKey(data, Key.right)) return KBD.right; - if (matchesKey(data, Key.pageUp)) return KBD.pageup; - if (matchesKey(data, Key.pageDown)) return KBD.pagedown; - if (matchesKey(data, Key.home)) return KBD.home; - if (matchesKey(data, Key.end)) return KBD.end; - if (matchesKey(data, Key.insert)) return KBD.insert; - if (matchesKey(data, Key.delete)) return KBD.delete; - if (matchesKey(data, Key.f1)) return KBD.f1; - if (matchesKey(data, Key.f2)) return KBD.f2; - if (matchesKey(data, Key.f3)) return KBD.f3; - if (matchesKey(data, Key.f4)) return KBD.f4; - if (matchesKey(data, Key.f5)) return KBD.f5; - if (matchesKey(data, Key.f6)) return KBD.f6; - if (matchesKey(data, Key.f7)) return KBD.f7; - if (matchesKey(data, Key.f8)) return KBD.f8; - if (matchesKey(data, Key.f9)) return KBD.f9; - if (matchesKey(data, Key.f10)) return KBD.f10; - if (matchesKey(data, Key.f11)) return KBD.f11; - if (matchesKey(data, Key.f12)) return KBD.f12; - - if (data.length === 1) { - const code = data.charCodeAt(0); - if (data >= "a" && data <= "z") return code - 32; - if (data >= "A" && data <= "Z") return code; - if (data >= "0" && data <= "9") return code; - } - return null; -} diff --git a/packages/coding-agent/examples/extensions/pi-dosbox/src/dosbox-instance.ts b/packages/coding-agent/examples/extensions/pi-dosbox/src/dosbox-instance.ts deleted file mode 100644 index d5da8664..00000000 --- a/packages/coding-agent/examples/extensions/pi-dosbox/src/dosbox-instance.ts +++ /dev/null @@ -1,438 +0,0 @@ -/** - * Persistent DOSBox Instance Manager - * - * Manages a singleton DOSBox instance that runs in the background. - * Provides API for sending keys, reading screen, and taking screenshots. - */ - -import { createRequire } from "node:module"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; -import { deflateSync } from "node:zlib"; -import type { CommandInterface, Emulators } from "emulators"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -let emulatorsInstance: Emulators | undefined; - -async function getEmulators(): Promise { - if (!emulatorsInstance) { - const require = createRequire(import.meta.url); - const distPath = dirname(require.resolve("emulators")); - await import("emulators"); - const g = globalThis as unknown as { emulators: Emulators }; - const emu = g.emulators; - emu.pathPrefix = `${distPath}/`; - emu.pathSuffix = ""; - emulatorsInstance = emu; - } - return emulatorsInstance; -} - -export interface DosboxState { - width: number; - height: number; - lastFrame: Uint8Array | null; - isGraphicsMode: boolean; -} - -// Emscripten FS type -interface EmscriptenFS { - mkdir(path: string): void; - writeFile(path: string, data: Uint8Array | Buffer): void; - readFile(path: string): Uint8Array; - readdir(path: string): string[]; - unlink(path: string): void; -} - -interface EmscriptenModule { - FS: EmscriptenFS; - _rescanFilesystem?: () => void; -} - -export class DosboxInstance { - private static instance: DosboxInstance | null = null; - - private ci: CommandInterface | null = null; - private state: DosboxState = { - width: 0, - height: 0, - lastFrame: null, - isGraphicsMode: false, - }; - private frameListeners: Set<(rgba: Uint8Array, width: number, height: number) => void> = new Set(); - private initPromise: Promise | null = null; - private disposed = false; - - private constructor() {} - - static async getInstance(): Promise { - if (!DosboxInstance.instance) { - DosboxInstance.instance = new DosboxInstance(); - await DosboxInstance.instance.init(); - } - return DosboxInstance.instance; - } - - static getInstanceSync(): DosboxInstance | null { - return DosboxInstance.instance; - } - - static async destroyInstance(): Promise { - if (DosboxInstance.instance) { - await DosboxInstance.instance.dispose(); - DosboxInstance.instance = null; - } - } - - private async init(): Promise { - if (this.initPromise) return this.initPromise; - - this.initPromise = (async () => { - const emu = await getEmulators(); - const bundle = await this.createBundle(emu); - this.ci = await emu.dosboxDirect(bundle); - - // Mount QBasic files after DOSBox starts - await this.mountQBasic(); - - const events = this.ci.events(); - - events.onFrameSize((width: number, height: number) => { - this.state.width = width; - this.state.height = height; - }); - - events.onFrame((rgb: Uint8Array | null, rgba: Uint8Array | null) => { - if (!this.state.width || !this.state.height) { - if (this.ci) { - this.state.width = this.ci.width(); - this.state.height = this.ci.height(); - } - } - - const rgbaFrame = rgba ?? (rgb ? this.expandRgbToRgba(rgb) : null); - if (rgbaFrame) { - this.state.lastFrame = rgbaFrame; - // Detect graphics mode by checking if we're in standard text resolution - // Text mode is typically 640x400 or 720x400 - this.state.isGraphicsMode = this.state.width !== 640 && this.state.width !== 720; - - for (const listener of this.frameListeners) { - listener(rgbaFrame, this.state.width, this.state.height); - } - } - }); - - events.onExit(() => { - this.disposed = true; - DosboxInstance.instance = null; - }); - })(); - - return this.initPromise; - } - - private async createBundle(emu: Emulators): Promise { - const bundle = await emu.bundle(); - // Simple autoexec - we mount files to /home/web_user which maps to C: - bundle.autoexec( - "@echo off", - "c:", - "cls", - "echo QuickBASIC 4.5 is at C:\\QB", - "echo Type: CD QB", - "echo Then: QB.EXE", - "echo.", - "dir", - ); - return bundle.toUint8Array(true); - } - - private async mountQBasic(): Promise { - if (!this.ci) return; - - // Access Emscripten module - const transport = (this.ci as unknown as { transport: { module: EmscriptenModule } }).transport; - const Module = transport.module; - const FS = Module.FS; - - // jsdos mounts C: to /home/web_user by default - // Let's verify and find the correct path - const mountPath = "/home/web_user"; - - // Create QB directory - const qbPath = `${mountPath}/QB`; - try { - FS.mkdir(qbPath); - } catch { - /* exists */ - } - - // Read QBasic files from the extension directory - const qbasicDir = join(__dirname, "..", "qbasic"); - const { readdirSync, readFileSync } = await import("node:fs"); - - const files = readdirSync(qbasicDir); - for (const file of files) { - if (file.startsWith(".")) continue; - try { - const data = readFileSync(join(qbasicDir, file)); - FS.writeFile(`${qbPath}/${file.toUpperCase()}`, data); - } catch (e) { - console.error(`Failed to mount ${file}:`, e); - } - } - - // Rescan so DOS sees the new files - if (Module._rescanFilesystem) { - Module._rescanFilesystem(); - } - } - - private expandRgbToRgba(rgb: Uint8Array): Uint8Array { - const rgba = new Uint8Array((rgb.length / 3) * 4); - for (let i = 0, j = 0; i < rgb.length; i += 3, j += 4) { - rgba[j] = rgb[i] ?? 0; - rgba[j + 1] = rgb[i + 1] ?? 0; - rgba[j + 2] = rgb[i + 2] ?? 0; - rgba[j + 3] = 255; - } - return rgba; - } - - isReady(): boolean { - return this.ci !== null && !this.disposed; - } - - getState(): DosboxState { - return { ...this.state }; - } - - getCommandInterface(): CommandInterface | null { - return this.ci; - } - - addFrameListener(listener: (rgba: Uint8Array, width: number, height: number) => void): void { - this.frameListeners.add(listener); - } - - removeFrameListener(listener: (rgba: Uint8Array, width: number, height: number) => void): void { - this.frameListeners.delete(listener); - } - - /** - * Send key events to DOSBox - */ - sendKeys(keys: string): void { - if (!this.ci) return; - - for (const key of keys) { - const keyCode = this.charToKeyCode(key); - if (keyCode !== null) { - const needsShift = this.needsShift(key); - if (needsShift) { - this.ci.sendKeyEvent(KBD.leftshift, true); - } - this.ci.sendKeyEvent(keyCode, true); - this.ci.sendKeyEvent(keyCode, false); - if (needsShift) { - this.ci.sendKeyEvent(KBD.leftshift, false); - } - } - } - } - - /** - * Send a special key (enter, backspace, etc.) - */ - sendSpecialKey(key: "enter" | "backspace" | "tab" | "escape" | "up" | "down" | "left" | "right" | "f5"): void { - if (!this.ci) return; - - const keyCode = KBD[key]; - if (keyCode) { - this.ci.sendKeyEvent(keyCode, true); - this.ci.sendKeyEvent(keyCode, false); - } - } - - /** - * Read text-mode screen content - */ - readScreenText(): string | null { - if (!this.ci) return null; - - try { - // Try to get screen text from emulators API - const text = (this.ci as unknown as { screenText?: () => string }).screenText?.(); - return text ?? null; - } catch { - return null; - } - } - - /** - * Get screenshot as PNG base64 - */ - getScreenshot(): { base64: string; width: number; height: number } | null { - if (!this.state.lastFrame || !this.state.width || !this.state.height) { - return null; - } - - const png = this.encodePng(this.state.width, this.state.height, this.state.lastFrame); - return { - base64: png.toString("base64"), - width: this.state.width, - height: this.state.height, - }; - } - - private encodePng(width: number, height: number, rgba: Uint8Array): Buffer { - const stride = width * 4; - const raw = Buffer.alloc((stride + 1) * height); - for (let y = 0; y < height; y++) { - const rowOffset = y * (stride + 1); - raw[rowOffset] = 0; - raw.set(rgba.subarray(y * stride, y * stride + stride), rowOffset + 1); - } - - const compressed = deflateSync(raw); - - const header = Buffer.alloc(13); - header.writeUInt32BE(width, 0); - header.writeUInt32BE(height, 4); - header[8] = 8; - header[9] = 6; - header[10] = 0; - header[11] = 0; - header[12] = 0; - - const signature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); - const ihdr = this.createChunk("IHDR", header); - const idat = this.createChunk("IDAT", compressed); - const iend = this.createChunk("IEND", Buffer.alloc(0)); - - return Buffer.concat([signature, ihdr, idat, iend]); - } - - private createChunk(type: string, data: Buffer): Buffer { - const length = Buffer.alloc(4); - length.writeUInt32BE(data.length, 0); - const typeBuffer = Buffer.from(type, "ascii"); - const crcBuffer = Buffer.concat([typeBuffer, data]); - const crc = this.crc32(crcBuffer); - const crcOut = Buffer.alloc(4); - crcOut.writeUInt32BE(crc, 0); - return Buffer.concat([length, typeBuffer, data, crcOut]); - } - - private crc32(buffer: Buffer): number { - let crc = 0xffffffff; - for (const byte of buffer) { - crc = CRC_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8); - } - return (crc ^ 0xffffffff) >>> 0; - } - - private charToKeyCode(char: string): number | null { - const lower = char.toLowerCase(); - if (lower >= "a" && lower <= "z") { - return lower.charCodeAt(0) - 32; // A-Z = 65-90 - } - if (char >= "0" && char <= "9") { - return char.charCodeAt(0); // 0-9 = 48-57 - } - if (char === " ") return KBD.space; - if (char === "\n" || char === "\r") return KBD.enter; - if (char === "\t") return KBD.tab; - // Common punctuation - const punct: Record = { - ".": 46, - ",": 44, - ";": 59, - ":": 59, // shift - "'": 39, - '"': 39, // shift - "-": 45, - _: 45, // shift - "=": 61, - "+": 61, // shift - "[": 91, - "]": 93, - "\\": 92, - "/": 47, - "!": 49, // shift+1 - "@": 50, // shift+2 - "#": 51, // shift+3 - $: 52, // shift+4 - "%": 53, // shift+5 - "^": 54, // shift+6 - "&": 55, // shift+7 - "*": 56, // shift+8 - "(": 57, // shift+9 - ")": 48, // shift+0 - }; - return punct[char] ?? null; - } - - private needsShift(char: string): boolean { - if (char >= "A" && char <= "Z") return true; - return '~!@#$%^&*()_+{}|:"<>?'.includes(char); - } - - async dispose(): Promise { - if (this.disposed) return; - this.disposed = true; - this.frameListeners.clear(); - - if (this.ci) { - const origLog = console.log; - const origError = console.error; - console.log = () => {}; - console.error = () => {}; - try { - await this.ci.exit(); - } catch { - /* ignore */ - } - setTimeout(() => { - console.log = origLog; - console.error = origError; - }, 100); - this.ci = null; - } - } -} - -// js-dos key codes -const KBD: Record = { - enter: 257, - backspace: 259, - tab: 258, - escape: 256, - space: 32, - leftshift: 340, - up: 265, - down: 264, - left: 263, - right: 262, - f5: 294, -}; - -const CRC_TABLE = createCrcTable(); - -function createCrcTable(): Uint32Array { - const table = new Uint32Array(256); - for (let i = 0; i < 256; i++) { - let c = i; - for (let j = 0; j < 8; j++) { - if (c & 1) { - c = 0xedb88320 ^ (c >>> 1); - } else { - c >>>= 1; - } - } - table[i] = c >>> 0; - } - return table; -}