Developer Index
Ark Luna. Never before OS experiment.
- Open source apps & App store
- Supporting Windows, Mac OS, Linux
- Fully-featured OS: your programs, your documents, 100% localhost
- Dev friendly, rich SDK, tons of pre-built components
An OS-as-an-app that runs on any platform, easily theme-able, that includes a GitHub-backed app store, private local hosting, a retro desktop UI, 100% terminal compatibility, that offers type safe APIs for app devs.
Ark Luna started as UI components to recreate the original theme of Windows XP in sharp HTML / CSS vectors, next came JS powered components, Tailwind CSS, Solid JS, then an HTTP server to read local files, even WebSocket for things like terminal emulation, games, realtime networking. The OS is as full featured for devs as any other platform, all open source, self hostable, or go remote on a VPS or EC2.
In the full OS, there are a few stock app on the store. These first party apps demonstrate how to use every feature of Ark Luna's SDK.
Quickstart
As an OS, Ark Luna is a Node JS console app running a localhost server. The UI is simply HTML, CSS, JS. The OS provides no database directly, but each app can use SQLite DBs in their app folder. Each app has a frontend folder, a backend folder - even the OS has a backend folder using the same APIs available to app devs. In a normal install the OS backend folder is /backend, your backend folder will be in /frontend/programs/v1/your-app-name/backend. Since the browser is limited, all your app functionality is in JS functions in your backend folder. Your frontend code can easily call from a type safe API client. Every install of Ark Luna has a /data folder that mimics the C:/ on desktops. Folders like Desktop, StartMenu, Downloads have extra functionality in the "Special Files API." Your frontend folder requires a package.json file; this file is also where app store information is read from. For your app to be in the store, it needs a filled out frontend/package.json. All programs are named @github-username/github-repo, but saved as folders like github-username.github-repo. Programs are always called by the official name, then converted to the folder name just before executing.
Frontend, HTML, CSS, Solid JS, Shadcn UI
Icons & Cursors
The icons & cursors folder is located at /frontend/src/assets. Inside, there are assets that represent a wide range of quality, sizes, use cases, time periods. There are many icons available to use. All are organized to show the UI language originally designed for.
Components
Our component list matches much of that you would expect of an OS, each component brings theming intended to represent a Windows XP-era look. The following is a list of UI components you can use in your programs:
| File Name | Purpose |
|---|---|
bar-menu.tsx | The alt-bar menu which contains typically File, Edit, View, etc. |
button.tsx | Hover, active, focus button. |
dialog.tsx | Creates a modal window that places your app in disabled mode. |
image.tsx | Used specifically for showing a sprite within a spritesheet. |
number-field.tsx | Hover, active, focus number-only input. |
select.tsx | Hover, active, focus dropdown. |
slider.tsx | A vertical or horizontal range input. |
status-bar.tsx | The below window content bar optionally visible to show status info. |
tabs.tsx | A tabbed menu showing only one content window at any given time. |
title-buttons.tsx | The minimize, maximize, close buttons available on window title bars. |
window.tsx | A draggable content frame that has width, height, z-index, position. |
(alpha) ribbon.tsx | A navigation & action bar full of commands, tabs, etc. |
UI APIs
These APIs are easily importable, callable from your frontend. They fulfill roles like file picker, path opener, new file creator, etc. Here are the list of APIs including their purpose.
| API names | Props | Purpose |
|---|---|---|
openApp | program: ProgramNameString, params: ProgramOpenParams | Open a specific app, passing in custom params. |
queryFileOpener | openPath: string | Look up what program should open a given file at a specific path using the file extension. |
openFile | openPath: string | Launch an app to open a given file at a path in its default program. |
openFilePicker | exts?: string[], isMulti?: true, startingDir?: string | Open the file picker to return an existing file the user intends to use in your app. |
openFileCreator | exts?: string[], isMulti?: true, startingDir?: string | Open the file create picker to return a new file the user intends to use in your app. |
Package JSON
Every application requires a package.json file in its frontend folder. This file serves multiple purposes:
- Defines the app's metadata (name, version, description, author)
- Specifies dependencies and scripts
- Contains app store information for publishing
- Configures UI-related settings like themes and icons
Here is an example of your package.json
{ "displayName": "Full App Name Here", "name": "@github-username/github.repo.here", "description": "Description of my app here", "author": { "name": "A pretty or brand styled form of your GitHub username" }, "version": "v0.0.1", "license": "MIT", "badges": [], "repository": {"url": "HTTP URL to GitHub repo here"}, "keywords": [], "contributors": [{"name": "Additionally, add names of contributors here"}], "icon": "Add a full HTTP URL to your icon image here"}Backend, Node JS, HTTP, POST, WebSocket
In your backend folder, there should be at least an index.ts, this file can export specific variables which will become HTTP APIs, HTTP POST APIs, or WebSocket APIs. Your backend can use NPM to install whatever packages your app needs. There is no need to install express or another HTTP or WS server, these are already provided for you. Your app can use Playwright to build your own browser, or xlsx.js to create a spreadsheets app, or even call out to Python to parse a .docx file.
Node JS
The backend in Ark Luna is built using Node JS and allows full access to the Node ecosystem. As a result, can utilize built-in modules like fs, path, and crypto, but also third-party packages from NPM.
HTTP POST
The best way to quickly create APIs for your frontend to use is this method. From your backend's index.ts, export the export const api object AKA the api constant. Each method inside the object will always take a single object param called props.
import fs from "fs/promises"
export const api = { listOutFilesHere = async (props: {somePath: string}) => { const fileList = await fs.readdir(props.somePath) return {fileList} }}To the API object, we added the function listOutFilesHere, gave it a parameter of somePath then returned an object that has a property of fileList: string[]. The API object can have as many functions as you'd like. The function can also have a props object as complicated as you'd like.
The frontend will use your API like this:
<IconButton icon="green-download" onClick={async () => { const {fileList} = await API.listOutFilesHere({somePath: self.currentPath}) console.log("The files listed", fileList) self.filesAtLocation = fileList }}/>Special Files API
Some functionality in the OS like the background image, app startup, task bar, start menu all store information in JSON files in the user's data folder.
Special files always have one of the following extensions:
| Extension | Meaning |
|---|---|
| .xp.json | Any shortcut to a file or a program. |
| .ti.json | Any taskbar shortcut to a program. |
| .di.json | One of the hardcoded OS system file. |
Folders can also be special, always located beneath /data:
| Folder | Meaning |
|---|---|
/data/Desktop | Holds desktop icons, taskbar icons, OS system files. |
/data/StartMenu | Holds strictly shortcuts, shown in the start menu. |
/data/StartMenu/Startup | Holds strictly shortcuts, opened on page load. |
This is an exhaustive list of all OS system files (in Desktop folder):
| File | Meaning |
|---|---|
Background.di.json | Holds the CSS related to displaying the background. |
Opener.di.json | Holds all the known file types including the program that opens them. |
Recent.di.json | Stores the recent files, programs opened for the start menu. |
HTTP Raw
To have more control over the HTTP request / response, you can export the export const any AKA the any constant to have direct control over the internal (req, res) => parameters. The HTTP server used to ingest your functions is Fastify JS, so all documentation is the same as an API for Fastify. The HTTP Raw does NOT integrate into the type safe API client, so you'll use fetch() or XHR in the frontend. However, it's specifically useful as the API client only makes POST requests. For some apps like music players or video servers, you need custom content type headers or HTTP methods, use the Raw API for these cases.
export const any = { async uploads(req: FastifyRequest, res: FastifyResponse) { try { const path = req.url.split("uploads/")[1] const buffer = await readFile(`${decodeURIComponent(path)}`) // Instead of using `return` use `res.send` from Fastify res.send(buffer) } catch (error) { res.status(404).send({ error: "File not found" }) } }}When to Use HTTP Raw
- Serving user-uploaded files (images, PDFs, videos, etc.).
- Streaming binary data directly to clients.
- Providing access to static assets without a structured API response.
- Handling non-JSON API endpoints where raw data is expected.
WebSocket
For real-time bidirectional communication, a set of WebSocket-based APIs is provided. You can export the export const ws AKA the ws constant. One example where it's used is the built-in SSH client - able to stream SSH input/output in real time. The ws constant has two properties, fromClient, fromServer defined in your backend. There is only one SSH connection per browser window for all apps to listen on, a router uses the type safe API in fromClient to know which messages are meant for which app. The fromServer object allows you to initialize a list of template messages to send to the browser window any time you invoke them.
const myConnections = new Mapexport const ws = { fromClient: { mySshSignin: async (props: {username: string, password: string}) => { const { clientId } = useWs(props) const sshConnection = await ssh.signin(props.username, props.password) myConnections.set(clientId, sshConnection) }, mySshCommand: async (props: {command: string}) => { const { clientId } = useWs(props) const sshConnection = myConnections.get(clientId) await sshConnection.send(props.command) } }, fromServer: { sshReadOutput: async (send: ({output: string}, clientId: string) => any) => { for (const clientId in myConnections) { const sshConnection = myConnections.get(clientId)
if (sshConnection.hasOutput) { const output = sshConnection.output send({output}, clientId) } } } }}const serverSideWsCommands = wsBindEndpoints(ws.fromServer)