Merge pull request #1592 from HeyPuter/DS/main

npm: release to npm
This commit is contained in:
Daniel Salazar
2025-09-19 13:58:50 -07:00
committed by GitHub
6 changed files with 543 additions and 79 deletions
+3 -1
View File
@@ -15,7 +15,7 @@ npm install @heyputer/puter.js
#### ES Modules
```js
import puter from '@heyputer/puterjs';
import '@heyputer/puter.js';
```
#### CommonJS
@@ -34,6 +34,8 @@ Include Puter.js directly in your HTML via CDN in the `<head>` section:
## Usage Example
After importing, you can use the global `puter` object:
```js
// Print a message
puter.print('Hello from Puter.js!');
+479
View File
@@ -0,0 +1,479 @@
declare global {
interface Window {
puter: Puter;
}
}
declare namespace Puter {
// Main Puter interface
interface Puter {
// Properties
appID: string;
env: 'app' | 'web' | 'gui';
// Utility methods
print(text: string, options?: { code?: boolean }): void;
randName(separator?: string): string;
exit(statusCode?: number): void;
// Sub-modules
ai: AI;
apps: Apps;
auth: Auth;
drivers: Drivers;
fs: FileSystem;
hosting: Hosting;
kv: KeyValue;
net: Networking;
perms: Permissions;
ui: UI;
workers: Workers;
}
// AI Module
interface AI {
chat(prompt: string, options?: ChatOptions): Promise<ChatResponse>;
chat(prompt: string, testMode?: boolean, options?: ChatOptions): Promise<ChatResponse>;
chat(prompt: string, imageURL?: string, testMode?: boolean, options?: ChatOptions): Promise<ChatResponse>;
chat(prompt: string, imageURLArray?: string[], testMode?: boolean, options?: ChatOptions): Promise<ChatResponse>;
chat(messages: ChatMessage[], testMode?: boolean, options?: ChatOptions): Promise<ChatResponse>;
img2txt(image: string | File | Blob, testMode?: boolean): Promise<string>;
txt2img(prompt: string, testMode?: boolean): Promise<HTMLImageElement>;
txt2img(prompt: string, options?: Txt2ImgOptions): Promise<HTMLImageElement>;
txt2speech(text: string): Promise<HTMLAudioElement>;
txt2speech(text: string, options?: Txt2SpeechOptions): Promise<HTMLAudioElement>;
txt2speech(text: string, language?: string): Promise<HTMLAudioElement>;
txt2speech(text: string, language?: string, voice?: string): Promise<HTMLAudioElement>;
txt2speech(text: string, language?: string, voice?: string, engine?: string): Promise<HTMLAudioElement>;
}
interface ChatOptions {
model?: string;
stream?: boolean;
max_tokens?: number;
temperature?: number;
tools?: ToolDefinition[];
}
interface ToolDefinition {
type: 'function';
function: {
name: string;
description: string;
parameters: object;
strict?: boolean;
};
}
interface ChatMessage {
role: 'system' | 'assistant' | 'user' | 'function' | 'tool';
content: string | ContentObject[];
tool_call_id?: string;
}
interface ContentObject {
type: 'text' | 'file';
text?: string;
puter_path?: string;
}
interface ChatResponse {
message: {
role: string;
content: string;
tool_calls?: ToolCall[];
};
}
interface ToolCall {
id: string;
function: {
name: string;
arguments: string;
};
}
interface Txt2ImgOptions {
model?: 'gpt-image-1' | 'gemini-2.5-flash-image-preview' | 'dall-e-3';
quality?: 'high' | 'medium' | 'low' | 'hd' | 'standard';
input_image?: string;
input_image_mime_type?: string;
}
interface Txt2SpeechOptions {
language?: string;
voice?: string;
engine?: 'standard' | 'neural' | 'generative';
}
// Apps Module
interface Apps {
create(name: string, indexURL: string): Promise<App>;
create(name: string, indexURL: string, title?: string): Promise<App>;
create(name: string, indexURL: string, title?: string, description?: string): Promise<App>;
create(options: CreateAppOptions): Promise<App>;
delete(name: string): Promise<App>;
get(name: string, options?: GetAppOptions): Promise<App>;
list(options?: ListAppOptions): Promise<App[]>;
update(name: string, attributes: UpdateAppAttributes): Promise<App>;
}
interface CreateAppOptions {
name: string;
indexURL: string;
title?: string;
description?: string;
icon?: string;
maximizeOnStart?: boolean;
filetypeAssociations?: string[];
}
interface GetAppOptions {
stats_period?: StatsPeriod;
icon_size?: null | 16 | 32 | 64 | 128 | 256 | 512;
}
interface ListAppOptions extends GetAppOptions { }
interface UpdateAppAttributes {
name?: string;
indexURL?: string;
title?: string;
description?: string;
icon?: string;
maximizeOnStart?: boolean;
filetypeAssociations?: string[];
}
type StatsPeriod = 'all' | 'today' | 'yesterday' | '7d' | '30d' | 'this_month' | 'last_month' | 'this_year' | 'last_year' | 'month_to_date' | 'year_to_date' | 'last_12_months';
interface App {
uid: string;
name: string;
icon: string;
description: string;
title: string;
maximize_on_start: boolean;
index_url: string;
created_at: string;
background: boolean;
filetype_associations: string[];
open_count: number;
user_count: number;
}
// Auth Module
interface Auth {
signIn(options?: { attempt_temp_user_creation?: boolean }): Promise<boolean>;
signOut(): void;
isSignedIn(): boolean;
getUser(): Promise<User>;
}
interface User {
uuid: string;
username: string;
email_confirmed: boolean;
}
// Drivers Module
interface Drivers {
call(interface: string, driver: string, method: string, args?: object): Promise<any>;
}
// FileSystem Module
interface FileSystem {
copy(source: string, destination: string, options?: CopyOptions): Promise<FSItem>;
delete(path: string, options?: DeleteOptions): Promise<void>;
getReadURL(path: string, expiresIn?: number): Promise<string>;
mkdir(path: string, options?: MkdirOptions): Promise<FSItem>;
move(source: string, destination: string, options?: MoveOptions): Promise<FSItem>;
read(path: string, options?: ReadOptions): Promise<Blob>;
readdir(path: string, options?: ReaddirOptions): Promise<FSItem[]>;
readdir(options?: ReaddirOptions): Promise<FSItem[]>;
rename(path: string, newName: string): Promise<FSItem>;
space(): Promise<SpaceInfo>;
stat(path: string): Promise<FSItem>;
upload(items: FileList | File[] | Blob[], dirPath?: string, options?: object): Promise<FSItem[]>;
write(path: string, data?: string | File | Blob, options?: WriteOptions): Promise<FSItem>;
}
interface CopyOptions {
overwrite?: boolean;
dedupeName?: boolean;
newName?: string;
}
interface DeleteOptions {
recursive?: boolean;
descendantsOnly?: boolean;
}
interface MkdirOptions {
overwrite?: boolean;
dedupeName?: boolean;
createMissingParents?: boolean;
}
interface MoveOptions extends CopyOptions {
createMissingParents?: boolean;
}
interface ReadOptions {
offset?: number;
byte_count?: number;
}
interface ReaddirOptions {
path?: string;
uid?: string;
}
interface WriteOptions {
overwrite?: boolean;
dedupeName?: boolean;
createMissingParents?: boolean;
}
interface SpaceInfo {
capacity: number;
used: number;
}
interface FSItem {
id: string;
uid: string;
name: string;
path: string;
is_dir: boolean;
parent_id: string;
parent_uid: string;
created: number;
modified: number;
accessed: number;
size: number | null;
writable: boolean;
read(): Promise<Blob>;
readdir(): Promise<FSItem[]>;
}
// Hosting Module
interface Hosting {
create(subdomain: string, dirPath?: string): Promise<Subdomain>;
delete(subdomain: string): Promise<boolean>;
get(subdomain: string): Promise<Subdomain>;
list(): Promise<Subdomain[]>;
update(subdomain: string, dirPath?: string): Promise<Subdomain>;
}
interface Subdomain {
uid: string;
subdomain: string;
root_dir: FSItem;
}
// KeyValue Module
interface KeyValue {
set(key: string, value: string | number | boolean | object | any[]): Promise<boolean>;
get(key: string): Promise<any>;
del(key: string): Promise<boolean>;
incr(key: string, amount?: number): Promise<number>;
decr(key: string, amount?: number): Promise<number>;
list(pattern?: string, returnValues?: boolean): Promise<string[] | KeyValuePair[]>;
list(returnValues?: boolean): Promise<string[] | KeyValuePair[]>;
flush(): Promise<boolean>;
}
interface KeyValuePair {
key: string;
value: any;
}
// Networking Module
interface Networking {
fetch(url: string, options?: RequestInit): Promise<Response>;
Socket: typeof Socket;
tls: {
TLSSocket: typeof TLSSocket;
};
}
class Socket {
constructor(hostname: string, port: number);
write(data: ArrayBuffer | Uint8Array | string): void;
close(): void;
on(event: 'open', callback: () => void): void;
on(event: 'data', callback: (buffer: Uint8Array) => void): void;
on(event: 'error', callback: (reason: string) => void): void;
on(event: 'close', callback: (hadError: boolean) => void): void;
}
class TLSSocket extends Socket {
constructor(hostname: string, port: number);
}
// Permissions Module
interface Permissions {
grantApp(app_uid: string, permissionString: string): Promise<object>;
grantAppAnyUser(app_uid: string, permissionString: string): Promise<object>;
grantGroup(group_uid: string, permissionString: string): Promise<object>;
grantOrigin(origin: string, permissionString: string): Promise<object>;
grantUser(username: string, permissionString: string): Promise<object>;
revokeApp(app_uid: string, permissionString: string): Promise<object>;
revokeAppAnyUser(app_uid: string, permissionString: string): Promise<object>;
revokeGroup(group_uid: string, permissionString: string): Promise<object>;
revokeOrigin(origin: string, permissionString: string): Promise<object>;
revokeUser(username: string, permissionString: string): Promise<object>;
}
// UI Module
interface UI {
alert(message?: string, buttons?: AlertButton[]): Promise<string>;
prompt(message?: string, defaultValue?: string): Promise<string | null>;
authenticateWithPuter(): Promise<boolean>;
contextMenu(options: ContextMenuOptions): void;
createWindow(options?: WindowOptions): void;
exit(statusCode?: number): void;
getLanguage(): Promise<string>;
hideSpinner(): void;
launchApp(appName?: string, args?: object): Promise<AppConnection>;
launchApp(options: LaunchAppOptions): Promise<AppConnection>;
on(eventName: 'localeChanged', handler: (data: { language: string }) => void): void;
on(eventName: 'themeChanged', handler: (data: ThemeData) => void): void;
onItemsOpened(handler: (items: FSItem[]) => void): void;
onLaunchedWithItems(handler: (items: FSItem[]) => void): void;
onWindowClose(handler: () => void): void;
parentApp(): AppConnection | null;
setMenubar(options: MenubarOptions): void;
setWindowHeight(height: number): void;
setWindowPosition(x: number, y: number): void;
setWindowSize(width: number, height: number): void;
setWindowTitle(title: string): void;
setWindowWidth(width: number): void;
setWindowX(x: number): void;
setWindowY(y: number): void;
showColorPicker(defaultColor?: string): Promise<string>;
showColorPicker(options?: object): Promise<string>;
showDirectoryPicker(options?: { multiple?: boolean }): Promise<FSItem | FSItem[]>;
showFontPicker(defaultFont?: string): Promise<{ fontFamily: string }>;
showFontPicker(options?: object): Promise<{ fontFamily: string }>;
showOpenFilePicker(options?: FilePickerOptions): Promise<FSItem | FSItem[]>;
showSaveFilePicker(data?: any, defaultFileName?: string): Promise<FSItem>;
showSpinner(): void;
socialShare(url: string, message?: string, options?: { left?: number; top?: number }): void;
wasLaunchedWithItems(): boolean;
}
interface AlertButton {
label: string;
value?: string;
type?: 'primary' | 'success' | 'info' | 'warning' | 'danger';
}
interface ContextMenuOptions {
items: (ContextMenuItem | '-')[];
}
interface ContextMenuItem {
label: string;
action?: () => void;
icon?: string;
icon_active?: string;
disabled?: boolean;
items?: (ContextMenuItem | '-')[];
}
interface WindowOptions {
center?: boolean;
content?: string;
disable_parent_window?: boolean;
has_head?: boolean;
height?: number;
is_resizable?: boolean;
show_in_taskbar?: boolean;
title?: string;
width?: number;
}
interface LaunchAppOptions {
name?: string;
args?: object;
}
interface ThemeData {
palette: {
primaryHue: number;
primarySaturation: string;
primaryLightness: string;
primaryAlpha: number;
primaryColor: string;
};
}
interface MenubarOptions {
items: MenuItem[];
}
interface MenuItem {
label: string;
action?: () => void;
items?: MenuItem[];
}
interface FilePickerOptions {
multiple?: boolean;
accept?: string | string[];
}
interface AppConnection {
usesSDK: boolean;
on(eventName: 'message', handler: (message: any) => void): void;
on(eventName: 'close', handler: (data: { appInstanceID: string }) => void): void;
off(eventName: string, handler: Function): void;
postMessage(message: any): void;
close(): void;
}
// Workers Module
interface Workers {
create(workerName: string, filePath: string): Promise<WorkerDeployment>;
delete(workerName: string): Promise<boolean>;
exec(workerURL: string, options?: WorkerExecOptions): Promise<Response>;
get(workerName: string): Promise<WorkerInfo>;
list(): Promise<WorkerInfo[]>;
}
interface WorkerDeployment {
success: boolean;
url: string;
errors: any[];
}
interface WorkerExecOptions extends RequestInit {
method?: string;
headers?: object;
body?: string | object;
cache?: RequestCache;
credentials?: RequestCredentials;
mode?: RequestMode;
redirect?: RequestRedirect;
referrer?: string;
signal?: AbortSignal;
}
interface WorkerInfo {
name: string;
url: string;
file_path: string;
file_uid: string;
created_at: string;
}
}
declare const puter: Puter.Puter;
export = Puter;
+14 -3
View File
@@ -2,7 +2,13 @@
"name": "@heyputer/puterjs",
"version": "2.0.0",
"description": "Puter.js - A JavaScript library for interacting with Puter services.",
"main": "index.js",
"main": "src/index.js",
"types": "index.d.ts",
"typings": "index.d.ts",
"files": [
"src/index.js",
"index.d.ts"
],
"type": "module",
"publishConfig": {
"registry": "https://registry.npmjs.org/"
@@ -11,13 +17,17 @@
"type": "git",
"url": "git+https://github.com/HeyPuter/puter.git"
},
"keywords": [
"puter",
"puter.js",
"puterjs"
],
"scripts": {
"start-server": "npx http-server --cors -c-1",
"start-webpack": "webpack && webpack --output-filename puter.dev.js --watch --devtool source-map",
"start": "concurrently \"npm run start-server\" \"npm run start-webpack\"",
"build": "webpack && { echo \"// Copyright 2024-present Puter Technologies Inc. All rights reserved.\"; echo \"// Generated on $(date '+%Y-%m-%d %H:%M')\n\"; cat ./dist/puter.js; } > temp && mv temp ./dist/puter.js"
},
"keywords": [],
"author": "Puter Technologies Inc.",
"license": "Apache-2.0",
"devDependencies": {
@@ -25,6 +35,7 @@
"webpack-cli": "^5.1.4"
},
"dependencies": {
"@heyputer/kv.js": "^0.1.92"
"@heyputer/kv.js": "^0.1.92",
"@heyputer/putility": "^1.0.3"
}
}
+11 -11
View File
@@ -11,15 +11,15 @@ import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default {
entry: './src/index.js',
output: {
filename: 'puter.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new webpack.DefinePlugin({
'globalThis.PUTER_ORIGIN': JSON.stringify(process.env.PUTER_ORIGIN || 'https://puter.com'),
'globalThis.PUTER_API_ORIGIN': JSON.stringify(process.env.PUTER_API_ORIGIN || 'https://api.puter.com'),
}),
],
entry: './src/index.js',
output: {
filename: 'puter.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new webpack.DefinePlugin({
'globalThis.PUTER_ORIGIN': JSON.stringify(process.env.PUTER_ORIGIN || 'https://puter.com'),
'globalThis.PUTER_API_ORIGIN': JSON.stringify(process.env.PUTER_API_ORIGIN || 'https://api.puter.com'),
}),
],
};
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@heyputer/putility",
"version": "1.0.2",
"version": "1.0.3",
"description": "",
"main": "index.js",
"scripts": {
@@ -10,4 +10,4 @@
},
"author": "Puter Technologies Inc.",
"license": "AGPL-3.0-only"
}
}
+34 -62
View File
@@ -1,60 +1,33 @@
/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
*
* This file is part of Puter.
*
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const { AdvancedBase } = require("../AdvancedBase");
const { TService } = require("../concepts/Service");
const { TeePromise } = require("../libs/promise");
const mkstatus = name => {
const c = class {
get label () { return name }
describe () { return name }
}
c.name = `Status${
name[0].toUpperCase() + name.slice(1)
}`
return c;
}
const { AdvancedBase } = require('../AdvancedBase');
const { TService } = require('../concepts/Service');
const StatusEnum = {
Registering: 'registering',
Pending: 'pending',
Initializing: 'initializing',
Running: 'running',
};
class ServiceManager extends AdvancedBase {
static StatusRegistering = mkstatus('registering');
static StatusPending = class StatusPending {
constructor ({ waiting_for }) {
this.waiting_for = waiting_for;
}
get label () { return 'waiting'; }
// TODO: trait?
describe () {
return `waiting for: ${this.waiting_for.join(', ')}`
}
}
static StatusInitializing = mkstatus('initializing');
static StatusRunning = class StatusRunning {
constructor ({ start_ts }) {
this.start_ts = start_ts;
}
get label () { return 'running'; }
describe () {
return `running (since ${this.start_ts})`;
}
}
constructor ({ context } = {}) {
constructor({ context } = {}) {
super();
this.context = context;
@@ -68,7 +41,7 @@ class ServiceManager extends AdvancedBase {
// initialized; mapped like: waiting_[dependency] = Set(dependents)
this.waiting_ = {};
}
async register (name, factory, options = {}) {
async register(name, factory, options = {}) {
await new Promise(rslv => setTimeout(rslv, 0));
const ins = factory.create({
@@ -78,25 +51,25 @@ class ServiceManager extends AdvancedBase {
const entry = {
name,
instance: ins,
status: new this.constructor.StatusRegistering(),
status: StatusEnum.Registering,
};
this.services_l_.push(entry);
this.services_m_[name] = entry;
await this.maybe_init_(name);
}
info (name) {
info(name) {
return this.services_m_[name];
}
get (name) {
get(name) {
const info = this.services_m_[name];
if ( ! info ) throw new Error(`Service not registered: ${name}`);
if ( ! (info.status instanceof this.constructor.StatusRunning ) ) {
if ( info.status !== StatusEnum.Running ) {
return undefined;
}
return info.instance;
}
async aget (name) {
async aget(name) {
await this.wait_for_init([name]);
return this.get(name);
}
@@ -105,7 +78,7 @@ class ServiceManager extends AdvancedBase {
* Wait for the specified list of services to be initialized.
* @param {*} depends - list of services to wait for
*/
async wait_for_init (depends) {
async wait_for_init(depends) {
let check;
await new Promise(rslv => {
@@ -134,7 +107,7 @@ class ServiceManager extends AdvancedBase {
});
};
get_waiting_for_ (depends) {
get_waiting_for_(depends) {
const waiting_for = [];
for ( const depend of depends ) {
const depend_entry = this.services_m_[depend];
@@ -142,14 +115,14 @@ class ServiceManager extends AdvancedBase {
waiting_for.push(depend);
continue;
}
if ( ! (depend_entry.status instanceof this.constructor.StatusRunning) ) {
if ( ( depend_entry.status !== StatusEnum.Running ) ) {
waiting_for.push(depend);
}
}
return waiting_for;
}
async maybe_init_ (name) {
async maybe_init_(name) {
const entry = this.services_m_[name];
const depends = entry.instance.as(TService).get_depends();
const waiting_for = this.get_waiting_for_(depends);
@@ -160,34 +133,33 @@ class ServiceManager extends AdvancedBase {
}
for ( const dependency of waiting_for ) {
/** @type Set */
const waiting_set = this.waiting_[dependency] ||
(this.waiting_[dependency] = new Set());
waiting_set.add(name);
if ( !this.waiting_[dependency] ) {
this.waiting_[dependency] = new Set();
}
this.waiting_[dependency].add(name);
}
entry.status = new this.constructor.StatusPending(
{ waiting_for });
entry.status = StatusEnum.Pending;
entry.statusWaitingFor = waiting_for;
}
// called when a service has all of its dependencies initialized
// and is ready to be initialized itself
async init_service_ (name, modifiers = {}) {
async init_service_(name, modifiers = {}) {
const entry = this.services_m_[name];
entry.status = new this.constructor.StatusInitializing();
entry.status = StatusEnum.Initializing;
const service_impl = entry.instance.as(TService);
await service_impl.init();
entry.status = new this.constructor.StatusRunning({
start_ts: new Date(),
});
entry.status = StatusEnum.Running;
entry.statusStartTS = new Date();
/** @type Set */
const maybe_ready_set = this.waiting_[name];
const promises = [];
if ( maybe_ready_set ) {
for ( const dependent of maybe_ready_set.values() ) {
promises.push(this.maybe_init_(dependent, {
no_init_listeners: true
no_init_listeners: true,
}));
}
}