puter.js SDK — TypeScript & documentation drift audit
Pinned to commit 535cf57 (branch main). Every finding below is reproducible against that tree.
Full deep audit of every API family — impl (src/puter-js/src/modules/ + src/index.js; UI param handling in src/gui/src/IPC.js), types (src/puter-js/types/), and docs (src/docs/src/).
Legend: 🟡 types (impl/docs have it, types don't/wrong) · 🔵 docs (impl/types have it, docs don't/wrong) · 🔴 both · 🐛 impl-bug (advertised param the impl ignores, or phantom typed method) · 🆕/🗑 manifest-drift (file added/removed vs the audit manifest).
⚠️ Structural change since last audit: several modules restructured flat file → directory
The biggest change this week is a large-scale refactor of single-file modules into directories (index.js + per-method files + lib/). The public surface is mostly unchanged, but the manifest's "flat .js" expectation is now stale, and the refactor surfaced several new, undocumented/untyped methods.
| Module |
Was (manifest) |
Now (disk) |
New methods surfaced |
| AI |
modules/AI.js |
modules/ai/ (index + chat, image, models, ocr, sts, stt, tts, video, lib/) |
— |
| Apps |
modules/Apps.js |
modules/apps/ |
checkName (documented), getDeveloperProfile (undocumented) |
| Hosting |
modules/Hosting.js |
modules/hosting/ |
— |
| KV |
modules/KV.js |
modules/kv/ |
— |
| OS |
modules/OS.js |
modules/os/ |
user, version (typed, no docs family — OK per manifest) |
| Perms |
modules/Perms.js |
modules/perms/ (appRootDir, folders, grants, groups, permissions) |
grant/revoke ×10, group ×4 (typed, undocumented), requestRead/WriteAppRootDir (untyped + undocumented) |
| Email |
(absent from manifest) |
modules/Email.js + email.d.ts |
send (typed, no docs family at all) |
🆕 src/docs/src/Objects/ttsengine.md, ttsvoice.md, and src/docs/src/Apps/checkName.md also exist beyond the manifest's Objects/Apps lists. 🗑 No manifest-listed impl/doc file is missing from disk.
Highest-impact shortlist (all families)
- 🐛 UI
alert() / prompt() / socialShare(url) hang forever on documented no-/minimal-arg forms. The IPC handlers gate ALERT/PROMPT on message !== undefined and read socialShare options.left without guarding options === undefined, so the documented minimal calls never post a response (or throw a TypeError) and the returned promise never settles. IPC.js:229,249,329.
- 🐛 AI
test_mode option silently dropped in 4 methods (txt2img, txt2speech, speech2txt, speech2speech). Types + docs promise { test_mode: true } returns a free sample, but only the positional boolean reaches the wire-level flag — the options-object field never does. txt2vid/img2txt promote it correctly, proving the omission. Users testing via the documented object form burn real credits.
- 🔴 KV
MAX_VALUE_SIZE is 399*1024 (399 KB / 408,576 B) but documented & typed as "400 KB" in 6 sites + 1 impl JSDoc. kv/lib/validate.js:4.
- 🔴 Hosting
create/update dirPath marked required but is optional, and update with no dirPath sends root_dir: null to disconnect the directory — a real capability hidden from types and docs. hosting/create.js:37, hosting/update.js:21-22.
- 🔴 Perms
requestReadAppRootDir / requestWriteAppRootDir are live public methods with zero type + zero doc coverage. perms/appRootDir.js:65,76.
- 🐛 UI
alert body_icon/icon and notify type/duration are documented options silently ignored on the desktop/app path (IPC forwards neither). IPC.js:238,283.
- 🔵 New public methods missing all docs:
puter.email.send, apps.getDeveloperProfile, workers.getLoggingHandle, 14 perms grant/revoke/group methods, UI toggleWindow/disableMenuItem/enableMenuItem.
- 🔴 AI
ChatResponseChunk is missing the image and error chunk shapes that chat.md documents and shows in streaming examples.
AI
| Function |
Item |
Issue |
Tag |
impl ref |
chat |
option vision |
ChatOptions.vision? typed but impl never reads userParams.vision (only sets it internally when media present) — phantom option |
🐛 |
chat.js:207-233 |
chat |
option provider |
impl reads + types declare, but chat.md omits it |
🔵 |
chat.js:20,231 |
chat |
option response |
impl copies, types declare, docs never mention |
🔵 |
chat.js:14 |
chat |
option context_management |
impl copies + typed (escape hatch), chat.md doesn't list |
🔵 |
chat.js:23 |
chat |
option driver (alias of provider) |
legacy alias in impl + types, docs don't mention |
🔵 |
chat.js:231-233 |
chat |
overload chat(prompt, testMode, options) |
documented + impl-supported, no matching TS overload (chat(p, true, {…}) type-errors) |
🟡 |
chat.js:113-123; ai.d.ts:428-433 |
chat |
ChatMessage.images |
non-optional, forcing every input message to carry images; should be optional |
🟡 |
ai.d.ts:14 |
chat (stream) |
chunk image |
chat.md documents part.image (type:"image_url") chunks; ChatResponseChunk + chatresponsechunk.md omit them |
🔴 |
ai.d.ts:107-130; chatresponsechunk.md:12-21 |
chat (stream) |
chunk error |
chat.md documents an error chunk w/ part.message; type + object doc omit it |
🔴 |
ai.d.ts:108-129; chatresponsechunk.md:12-21 |
chat |
ChatResponse.choices |
typed but chatresponse.md doesn't list choices |
🔵 |
ai.d.ts:92 |
txt2img |
option test_mode |
type + doc advertise it, impl never promotes options.test_mode to wire flag (positional only) |
🐛 |
image.js:38-50; ai.d.ts:223; txt2img.md:36 |
txt2img |
Replicate per-model options |
doc lists guidance,go_fast,output_quality,output_megapixels,safety_tolerance,prompt_upsampling,generation_mode,style,contrast,prompt_enhance — none on Txt2ImgOptions |
🟡 |
image.js:68-77; txt2img.md:134-149 |
txt2img |
option input_image_mime_type |
typed + used in doc example, not in any options table |
🔵 |
ai.d.ts:179 |
txt2img |
options service/driver |
typed, docs list neither (only provider) |
🔵 |
ai.d.ts:180-182 |
txt2speech |
option test_mode |
type + doc advertise, impl only checks positional booleans |
🐛 |
tts.js:111; ai.d.ts:288 |
speech2txt |
option test_mode |
type + doc advertise, impl never promotes options.test_mode |
🐛 |
stt.js:59-83; ai.d.ts:397 |
speech2speech |
option test_mode |
type + doc advertise, impl positional-only |
🐛 |
sts.js:74-98; ai.d.ts:421 |
speech2speech |
camelCase aliases |
impl accepts voiceId,modelId,outputFormat,voiceSettings,fileFormat,removeBackgroundNoise,optimizeStreamingLatency,enableLogging; none typed/documented — and speech2speech.md:90 example uses removeBackgroundNoise |
🔴 |
sts.js:16-41; ai.d.ts:408-422 |
speech2speech |
option provider |
typed + forwarded, docs list omits |
🔵 |
ai.d.ts:411 |
txt2vid |
option duration (alias seconds) |
impl maps duration→seconds, typed, txt2vid.md documents only seconds |
🔵 |
video.js:66-68; ai.d.ts:238 |
listModelProviders |
AI.md index |
has doc page + TS decl, omitted from AI.md Functions list |
🔵 |
ai.d.ts:426; AI.md:194-203 |
FileSystem
| Function |
Item |
Issue |
Tag |
impl ref |
move |
dedupeName option |
docs list it, but move.js builds body with no dedupe field — silently ignored |
🐛 |
move.js:49-60; FS/move.md:34 |
move |
newName option |
impl reads + typed, docs never mention |
🔵 |
move.js:31; FS/move.md:29-35 |
move |
callback overload |
copy has (src,dst,options,success,error) overload; move doesn't |
🟡 |
move.js:26; filesystem.d.ts:226 |
move |
newMetadata |
impl reads (newMetadata/new_metadata) + typed, undocumented |
🔵 |
move.js:57 |
read |
cache option |
impl reads options.cache; absent from ReadOptions type + docs |
🔴 |
read.js:45; filesystem.d.ts:81-88 |
read |
options+callbacks overload |
impl supports combined form; type has only separate forms |
🟡 |
filesystem.d.ts:240-242 |
stat |
returnWorkers option |
impl reads (OR'd into return_subdomains); missing from type + docs |
🔴 |
stat.js:58,73 |
stat |
consistency option |
typed + read, not documented |
🔵 |
stat.js:37 |
readdir |
recursive option |
impl sends it; missing from type + docs |
🔴 |
readdir.js:42 |
readdir |
depth option |
impl sends it; missing from type + docs |
🔴 |
readdir.js:43 |
readdir |
no_subdomains option |
impl reads it; missing from type |
🟡 |
readdir.js:36 |
readdir |
consistency/no_thumbs/no_assocs |
typed + read, undocumented |
🔵 |
readdir.js:35-42 |
mkdir |
shortcutTo option |
typed + read (shortcut_to), not documented |
🔵 |
mkdir.js:39 |
mkdir |
rename alias |
impl accepts rename alias of dedupeName; doc lists only dedupeName |
🔵 |
mkdir.js:38 |
mkdir |
recursive alias |
impl accepts recursive alias of createMissingParents; doc lists only createMissingParents |
🔵 |
mkdir.js:41 |
readdirSubdomains |
existence |
wired as puter.fs.readdirSubdomains but not on FS type + undocumented |
🔴 |
readdirSubdomains.js; index.js:46 |
sign |
docs |
impl + type present, no FS/sign.md, not in FS.md list |
🔵 |
sign.js; filesystem.d.ts:288 |
revokeReadURL |
docs |
impl + type present, no doc page, not in FS.md list |
🔵 |
revokeReadUrl.js; filesystem.d.ts:303 |
getReadURL |
FS.md index |
has doc page but omitted from FS.md Functions index |
🔵 |
FS.md:299-308 |
space |
example |
space.md example calls puter.space() — no such top-level alias |
🔵 |
FS/space.md:34 |
upload |
init/start/progress/abort callbacks |
typed + read, upload.md documents none |
🔵 |
upload/index.js:87-91 |
upload |
createMissingAncestors/createFileParent/shortcutTo/appUID/strict/name |
typed + read, undocumented |
🔵 |
filesystem.d.ts:152-160 |
write |
data-less write(path) overload |
impl + docs allow it, but type makes data required |
🟡 |
write.js:71-74; filesystem.d.ts:285 |
write |
createMissingAncestors + callbacks |
typed, not in write.md |
🔵 |
write.js:37,45 |
FSItem |
uid/uuid/isDirectory |
impl + type expose them; fsitem.md documents only id |
🔵 |
FSItem.js:53-54,104 |
FSItem |
readURL/writeURL/metadataURL |
public on impl + type, undocumented |
🔵 |
FSItem.js:19-33 |
FSItem.write |
data param type |
docs say String|File|Blob; impl + type also accept ArrayBuffer/view |
🔵 |
FSItem.js:169 |
KV
| Function |
Item |
Issue |
Tag |
impl ref |
MAX_VALUE_SIZE |
constant value |
is 399*1024 (408,576 B) but documented/typed as "400 KB" in 6 sites |
🔴 |
kv/lib/validate.js:4; kv.d.ts:16,29,157; KV/set.md:29,37; kv/index.js:34 |
list |
offset-only / includeTotal-only |
impl returns KVListPage when any of limit/cursor/offset/includeTotal/fetchUntilFull set; type pagination overload requires limit or cursor → list({offset}) typed string[] but runtime is KVListPage |
🟡 |
kv/list.js:177-182; kv.d.ts:115-117 |
add |
object-form overload |
impl accepts add({key, pathAndValueMap, optConfig}); absent from types + docs |
🟡🔵 |
kv/add.js:29,37 |
incr/decr |
object-form overload |
impl accepts {key, pathAndAmountMap, optConfig}; absent from types + docs |
🟡🔵 |
kv/lib/args.js:53-59 |
get/del/flush |
object-form overloads |
JSDoc says accepts object form; no type/doc overload |
🟡🔵 |
kv/get.js:41 |
clear |
alias undocumented |
clear = alias of flush in impl + types, not in KV.md/flush.md |
🔵 |
kv/index.js:53-54 |
decr |
64-bit note |
incr documents 64-bit limit; decr omits it (same backend limit) |
🔵🟡 |
KV/decr.md:7 |
KVValue |
type quality |
string|number|boolean|object|unknown collapses to unknown |
🟡 |
kv.d.ts:2 |
(MAX_KEY_SIZE = 1024 = "1 KB" is consistent. add/incr/decr defaults, set-batch overloads, list pattern semantics, expire/expireAt, KVListPage/KVPair object docs all match.)
Apps
| Function |
Item |
Issue |
Tag |
impl ref |
getDeveloperProfile |
whole method |
NEW, exported + bound + typed (apps.d.ts:208), no doc page, absent from Apps.md list |
🆕🔵 |
apps/getDeveloperProfile.js |
create |
title param |
doc labels "(required)" — it's optional (falls back to name) |
🔵 |
apps/create.js:42; Apps/create.md:34 |
create |
indexURL param |
doc says "not provided → no index page"; impl throws 'Index URL is required' |
🔵 |
apps/create.js:56; Apps/create.md:25 |
get |
options-only overload |
impl supports get({stats_period,…}) with no name; types only get(name, options?) |
🟡🔵 |
apps/get.js:25-27 |
App.users |
user_email |
app.md says users are {username, user_uuid}; type AppUser also has optional user_email |
🔵 |
apps.d.ts:4-16 |
(checkName is new vs the manifest but fully wired — doc + type + list all present.)
Auth
| Function |
Item |
Issue |
Tag |
impl ref |
getUser |
sync throw |
non-async fn throws 401 before returning its Promise → getUser().catch() throws instead of rejecting; whoami (async) rejects correctly |
🐛 |
Auth.js:211-218 |
signIn |
request_auth option |
public option in impl JSDoc, absent from type options |
🟡 |
Auth.js:36; auth.d.ts:112 |
signIn |
request_auth option |
not documented in signIn.md Parameters |
🔵 |
signIn.md:26-30 |
getUser |
params/overloads |
docs say "Parameters: None", impl + types accept options + (success, error) |
🔵 |
getUser.md:15-18 |
whoami |
docs |
public in types + impl, no doc page |
🔵 |
Auth.js:259 |
getMonthlyUsage/getDetailedAppUsage |
Auth.md index |
have doc pages + types but omitted from Auth.md Functions list |
🔵 |
Auth.md:100-103 |
User object |
feature_flags/hasDevAccountAccess/otp |
interface fields not in user.md |
🔵 |
auth.d.ts:15,16,21 |
Perms
| Function |
Item |
Issue |
Tag |
impl ref |
requestReadAppRootDir |
existence |
exposed on puter.perms but neither typed nor documented |
🔴🆕 |
perms/appRootDir.js:65; index.js:87 |
requestWriteAppRootDir |
existence |
exposed but neither typed nor documented |
🔴🆕 |
perms/appRootDir.js:76; index.js:88 |
grantUser/grantGroup/grantApp/grantAppAnyUser/grantOrigin |
docs |
typed (perms.d.ts:2-6), no doc pages, absent from Perms.md list |
🔵🆕 |
perms/grants.js:15-61 |
revokeUser/revokeGroup/revokeApp/revokeAppAnyUser/revokeOrigin |
docs |
typed (perms.d.ts:8-12), undocumented |
🔵🆕 |
perms/grants.js:72-118 |
createGroup/addUsersToGroup/removeUsersFromGroup/listGroups |
docs |
typed (perms.d.ts:14-17), undocumented |
🔵🆕 |
perms/groups.js:13-46 |
requestPermission |
existence |
deprecated alias of request; not typed, not documented |
🟡 |
perms/permissions.js:23 |
| Perms.md |
platforms frontmatter |
Perms.md declares [apps]; method pages declare [websites, apps] |
🔵 |
Perms.md:5 |
Email (new module — no docs family)
| Function |
Item |
Issue |
Tag |
impl ref |
send |
docs family |
public + fully typed (puter.email.send), but no src/docs/src/Email* exists at all |
🔵🆕 |
Email.js:51; index.js:292 |
(No type drift: positional + options overloads and EmailSendOptions/EmailSendResult/EmailAttachment interfaces align with impl.)
UI (impl in UI.js / IPC.js = src/gui/src/IPC.js)
| Function |
Item |
Issue |
Tag |
impl ref |
alert |
message undefined |
alert() with no message → IPC gated message !== undefined → no response, promise hangs forever |
🐛 |
IPC.js:229 |
alert |
body_icon/icon |
typed + documented, but IPC forwards only type → silently ignored on desktop path |
🐛 |
IPC.js:230-238 |
prompt |
message undefined |
prompt() with no message hangs forever (same IPC gate) |
🐛 |
IPC.js:249 |
prompt |
options.defaultValue |
impl/types/IPC support it; prompt.md never documents the options arg |
🔵 |
UI.js:857; IPC.js:253 |
socialShare |
options omitted |
documented socialShare(url) → IPC reads options.left unguarded → TypeError, promise hangs |
🐛 |
IPC.js:329 |
socialShare |
return type |
types say void; impl returns a Promise (which never resolves — no original_msg_id reply) |
🟡 |
UI.js:844 |
notify |
type ignored |
documented option never read by IPC showNotification |
🐛 |
IPC.js:283 |
notify |
duration ignored |
documented option never read/forwarded (no-op on desktop) |
🐛 |
IPC.js:283 |
notify |
return value |
fallback resolves uid || null; type says Promise<string> |
🟡 |
UI.js:906-908 |
showSpinner |
ref-count claim |
types + docs claim ref-counted "until all instances hidden"; impl early-returns, single hideSpinner removes overlay |
🔴 |
UI.js:2298 |
contextMenu |
theme option |
typed + impl reads spec.theme; contextMenu.md never mentions |
🔵 |
UI.js:1723 |
contextMenu |
x/y position |
impl reads spec.x/spec.y; neither typed nor documented |
🟡🔵 |
UI.js:1726-1727 |
setMenubar |
theme option |
typed + impl reads spec.theme; setMenubar.md never mentions |
🔵 |
UI.js:1364 |
setMenubar |
item id field |
MenuItem.id in types + required for setMenuItemChecked/etc; docs don't document |
🔵 |
ui.d.ts:133 |
toggleWindow |
existence |
public method in impl, absent from types + docs |
🆕 |
UI.js:1342 |
disableMenuItem |
existence |
public impl + IPC handler, absent from types + docs |
🆕 |
UI.js:1659 |
enableMenuItem |
existence |
public impl + IPC handler, absent from types + docs |
🆕 |
UI.js:1669 |
setMenuItemIcon/IconActive/Checked |
docs |
in types + impl (with IPC handlers), no doc files, not in UI.md |
🔵 |
UI.js:1680,1692,1703 |
requestPermission |
docs |
typed + impl + IPC handler, no doc / UI.md entry |
🔵 |
UI.js:1377; IPC.js:1314 |
requestUpgrade |
docs |
typed + impl, undocumented (borderline) |
🔵 |
UI.js:1064 |
exit |
UI.md label |
UI.md lists puter.ui.exit() — no such UI method; actual API is puter.exit() (exit.md is correct) |
🔵 |
UI.md:36 |
contextMenu/hideWindow/showWindow |
UI.md index |
doc files exist but are not listed in the UI.md "Available Functions" index |
🔵🗑 |
UI.md |
setWindowX/setWindowY/setWindowPosition |
"positive number" |
docs say x/y "Must be a positive number"; impl clamps to viewport (y floored at taskbar_height, not 0) |
🔵 |
IPC.js:1092-1118 |
on('connection') |
replay claim |
grouped with localeChanged/themeChanged as "called immediately with most recent value", but connection is never stored in #lastBroadcastValue → never replays |
🟡🔵 |
UI.js:586-619 |
launchApp |
#(as) pseudonym |
impl supports #(as) split in appName; undocumented in types + docs |
🔵 |
UI.js:1957 |
launchApp |
app_name alias |
LaunchAppOptions.app_name typed (alias for name); docs only mention name |
🔵 |
ui.d.ts:90 |
Workers
| Function |
Item |
Issue |
Tag |
impl ref |
getLoggingHandle |
docs |
public + typed (workers.d.ts:64) but absent from all docs (incl. Workers.md list) |
🔵 |
Workers.js:233 |
getLoggingHandle |
close() unbound |
logStreamObject.close = socket.close (unbound) → handle.close() throws TypeError: Illegal invocation; sibling cancel binds correctly |
🐛 |
Workers.js:270 |
getLoggingHandle |
getLoggingUrl args dropped |
makeDriverMethod called with no argNames then invoked with 2 positional args → driverArgs is {}, auth token + worker name silently discarded |
🐛 |
Workers.js:234 |
getLoggingHandle |
log event |
log event / onLog / close / stream coercion documented nowhere |
🔵 |
Workers.js:264-270 |
exec |
x-puter-no-auth header |
opt-out (unauthenticated request) header documented only in source JSDoc, not exec.md |
🔵 |
Workers.js:97-100 |
exec |
options optionality |
exec.md options not tagged (Optional); type marks init? optional |
🔵 |
Workers.js:93; workers.d.ts:52 |
list |
empty-object call |
list({}) matches no overload (bare list()), unlike hosting's list(options?) |
🟡 |
Workers.js:141; workers.d.ts:63 |
(router.md/types.md describe the deployed-worker runtime — verified, internally consistent, not mapped to Workers.js.)
Hosting
| Function |
Item |
Issue |
Tag |
impl ref |
create |
dirPath optionality |
impl optional — create(subdomain) alone yields {subdomain}; types declare dirPath: string required + create.md marks it (required) |
🔴 |
hosting/create.js:31,37-39 |
update |
dirPath optionality + disconnect |
impl optional; omitting it sends root_dir: null to disconnect the directory; type requires it + update.md never mentions the disconnect behavior |
🔴 |
hosting/update.js:16,21-22 |
create |
object-form root_dir optionality |
impl passes object as-is (root_dir effectively optional); published overload + types + create.md mark it required |
🟡 |
hosting/create.js:27,40-42 |
(get/delete + Subdomain object doc match. No camelCase rootDir alias — snake_case root_dir only, consistent.)
Networking
| Function |
Item |
Issue |
Tag |
impl ref |
Socket |
'drain' event |
declared SocketEvent + registered in EventListener but never emitted anywhere — a consumer wiring addListener('drain') waits forever |
🟡 |
PSocket.js:28; networking.d.ts:7,35 |
Socket |
on('drain') overload |
union includes 'drain' but there is no on(event:'drain', …) overload (self-inconsistent type) |
🟡 |
networking.d.ts:7 vs 27-33 |
net.generateWispV1URL |
docs |
public typed method on puter.net, absent from Networking.md Functions |
🔵 |
index.js:759; networking.d.ts:62 |
net.fetch |
input/url param |
type + impl accept RequestInfo|URL (incl. a Request); docs describe first arg as url (String) only |
🔵 |
requests.js:47; fetch.md:18 |
TLSSocket |
'error' payload |
read-loop emit('error', e) forwards a raw caught value (may be non-Error); type/docs promise Error |
🟡 |
PTLS.js:92; networking.d.ts:31 |
(Socket/TLSSocket constructors, write/close/addListener, open/data/error/close + tls* events all match.)
Peer
| Function |
Item |
Issue |
Tag |
impl ref |
serve/connect |
options.port |
impl reads + forwards options.port (server-create & client-connect), but missing from PuterPeerOptions and both docs |
🔴 |
Peer.js:98,286; peer.d.ts:1-6 |
connection 'error' |
.error field |
typed readonly error: string, but impl always emits new Error(...) / RTCError; docs treat it as an object (event.error?.message) — type is wrong, should be Error/unknown |
🟡 |
Peer.js:348,277,327; peer.d.ts:42-44 |
connection 'message' |
.data field |
typed ArrayBuffer|string; data channel sets no binaryType, so binary frames arrive as Blob (omitted) |
🟡 |
Peer.js:227-230; peer.d.ts:28-30 |
AppConnection object |
doc ownership |
Objects/AppConnection.md documents the puter.ui app-connection (usesSDK/postMessage/close), unrelated to peer.d.ts — naming collision; no Objects page for PuterPeerConnection/PuterPeerServer |
🔵 |
AppConnection.md; peer.d.ts:94 |
(ensureTurnRelays, serve/connect returns, iceServers/forceRelay, PuterPeerServer/PuterPeerConnection surfaces + connection/open/close events all match.)
OS / Utils / Drivers
| Function |
Item |
Issue |
Tag |
impl ref |
Utils print |
escapeHTML option |
impl + types expose it; undocumented in Utils/print.md |
🔵 |
index.js:1443 |
Utils print |
variadic params |
impl variadic (...args); types declare single text: string |
🟡 |
index.js:1436; puter.d.ts:92 |
Utils env |
doc value list |
Utils/env.md lists only app/web/gui; impl + type also yield nodejs/web-worker/service-worker |
🔵 |
index.js:459-480 |
Utils puter.on/off |
existence |
public host-app event API absent from puter.d.ts |
🟡 |
index.js:1154,1161 |
Drivers.call |
4-arg overload |
impl accepts (iface, implementation, method, parameters); types declare only 2/3-arg |
🟡 |
Drivers.js:84-88; drivers.d.ts:9-10 |
(OS user/version match types cleanly — no OS docs family, correct per manifest. randName/appID/exit match.)
🐛 impl-bug bucket (code fixes, not doc/type edits)
| # |
Bug |
ref |
| 1 |
UI alert()/prompt() with no message never get an IPC response → promise hangs forever (handlers gated on message !== undefined) |
IPC.js:229,249 |
| 2 |
UI socialShare(url) (documented minimal form) reads options.left without guarding options === undefined → TypeError, promise hangs |
IPC.js:329 |
| 3 |
UI alert body_icon/icon never forwarded to UIAlert — silently ignored on desktop path |
IPC.js:238 |
| 4 |
UI notify type and duration never forwarded to UINotification — silently ignored on desktop path |
IPC.js:283 |
| 5 |
UI showSpinner early-returns instead of ref-counting; a single hideSpinner removes the overlay, contradicting the documented "until all instances hidden" |
UI.js:2298 |
| 6 |
AI test_mode options-object field never promoted to the wire-level flag in txt2img/txt2speech/speech2txt/speech2speech (positional boolean only); txt2vid/img2txt do it correctly |
image.js:38-50, tts.js:111, stt.js:59-83, sts.js:74-98 |
| 7 |
AI chat ChatOptions.vision? typed but never read from user options (only set internally when media present) — phantom option |
chat.js:207-233 |
| 8 |
FS move dedupeName documented but request body never includes a dedupe field — option is a no-op |
move.js:49-60 |
| 9 |
Auth getUser (non-async) throws the 401 before returning its Promise → getUser().catch() throws synchronously instead of rejecting (whoami handles it correctly) |
Auth.js:211-218 |
| 10 |
Workers getLoggingHandle().close() is socket.close copied unbound → TypeError: Illegal invocation when the documented close path is used |
Workers.js:270 |
| 11 |
Workers getLoggingHandle builds the getLoggingUrl driver method with no argNames, then passes 2 positional args → both auth token and worker name are dropped from the driver call (driverArgs = {}) |
Workers.js:234 |
| 12 |
KV assertKeySize/assertValueSize test .length (UTF-16 element count), not byte size; multibyte strings under-enforce, and non-string/array values have undefined .length so the value-size check is skipped entirely (pre-existing, flagged as a correctness gap) |
kv/lib/validate.js:17-28 |
🆕/🗑 manifest-drift
- 🆕 Flat→directory restructure of
AI, Apps, Hosting, KV, OS, Perms modules (each now <module>/index.js + per-method files + lib/). Public surface mostly unchanged; the manifest's "flat .js" entries are stale.
- 🆕
Email module — modules/Email.js + types/modules/email.d.ts, wired at index.js:292; entirely absent from the manifest, and no docs family exists.
- 🆕 New public methods from the restructure:
apps/getDeveloperProfile.js (undocumented), apps/checkName.js (documented), os/user.js + os/version.js (typed, no docs — OK per manifest), perms/{appRootDir,grants,groups}.js (grant/revoke/group typed-undocumented; requestRead/WriteAppRootDir untyped+undocumented).
- 🆕 New docs beyond manifest:
Objects/ttsengine.md, Objects/ttsvoice.md, Apps/checkName.md.
- 🗑 None — no manifest-listed impl or doc file is missing from disk.
puter.js SDK — TypeScript & documentation drift audit
Pinned to commit
535cf57(branchmain). Every finding below is reproducible against that tree.Full deep audit of every API family — impl (
src/puter-js/src/modules/+src/index.js; UI param handling insrc/gui/src/IPC.js), types (src/puter-js/types/), and docs (src/docs/src/).Legend: 🟡 types (impl/docs have it, types don't/wrong) · 🔵 docs (impl/types have it, docs don't/wrong) · 🔴 both · 🐛 impl-bug (advertised param the impl ignores, or phantom typed method) · 🆕/🗑 manifest-drift (file added/removed vs the audit manifest).
The biggest change this week is a large-scale refactor of single-file modules into directories (
index.js+ per-method files +lib/). The public surface is mostly unchanged, but the manifest's "flat.js" expectation is now stale, and the refactor surfaced several new, undocumented/untyped methods.modules/AI.jsmodules/ai/(index + chat, image, models, ocr, sts, stt, tts, video, lib/)modules/Apps.jsmodules/apps/checkName(documented),getDeveloperProfile(undocumented)modules/Hosting.jsmodules/hosting/modules/KV.jsmodules/kv/modules/OS.jsmodules/os/user,version(typed, no docs family — OK per manifest)modules/Perms.jsmodules/perms/(appRootDir, folders, grants, groups, permissions)requestRead/WriteAppRootDir(untyped + undocumented)modules/Email.js+email.d.tssend(typed, no docs family at all)🆕
src/docs/src/Objects/ttsengine.md,ttsvoice.md, andsrc/docs/src/Apps/checkName.mdalso exist beyond the manifest's Objects/Apps lists. 🗑 No manifest-listed impl/doc file is missing from disk.Highest-impact shortlist (all families)
alert()/prompt()/socialShare(url)hang forever on documented no-/minimal-arg forms. The IPC handlers gateALERT/PROMPTonmessage !== undefinedand readsocialShareoptions.leftwithout guardingoptions === undefined, so the documented minimal calls never post a response (or throw aTypeError) and the returned promise never settles.IPC.js:229,249,329.test_modeoption silently dropped in 4 methods (txt2img,txt2speech,speech2txt,speech2speech). Types + docs promise{ test_mode: true }returns a free sample, but only the positional boolean reaches the wire-level flag — the options-object field never does.txt2vid/img2txtpromote it correctly, proving the omission. Users testing via the documented object form burn real credits.MAX_VALUE_SIZEis399*1024(399 KB / 408,576 B) but documented & typed as "400 KB" in 6 sites + 1 impl JSDoc.kv/lib/validate.js:4.create/updatedirPathmarked required but is optional, andupdatewith nodirPathsendsroot_dir: nullto disconnect the directory — a real capability hidden from types and docs.hosting/create.js:37,hosting/update.js:21-22.requestReadAppRootDir/requestWriteAppRootDirare live public methods with zero type + zero doc coverage.perms/appRootDir.js:65,76.alertbody_icon/iconandnotifytype/durationare documented options silently ignored on the desktop/app path (IPC forwards neither).IPC.js:238,283.puter.email.send,apps.getDeveloperProfile,workers.getLoggingHandle, 14permsgrant/revoke/group methods, UItoggleWindow/disableMenuItem/enableMenuItem.ChatResponseChunkis missing theimageanderrorchunk shapes thatchat.mddocuments and shows in streaming examples.AI
chatvisionChatOptions.vision?typed but impl never readsuserParams.vision(only sets it internally when media present) — phantom optionchatproviderchatresponsechatcontext_managementchatdriver(alias ofprovider)chatchat(prompt, testMode, options)chat(p, true, {…})type-errors)chatChatMessage.imagesimages; should be optionalchat(stream)imagepart.image(type:"image_url") chunks;ChatResponseChunk+ chatresponsechunk.md omit themchat(stream)errorerrorchunk w/part.message; type + object doc omit itchatChatResponse.choiceschoicestxt2imgtest_modeoptions.test_modeto wire flag (positional only)txt2imgguidance,go_fast,output_quality,output_megapixels,safety_tolerance,prompt_upsampling,generation_mode,style,contrast,prompt_enhance— none onTxt2ImgOptionstxt2imginput_image_mime_typetxt2imgservice/driverprovider)txt2speechtest_modespeech2txttest_modeoptions.test_modespeech2speechtest_modespeech2speechvoiceId,modelId,outputFormat,voiceSettings,fileFormat,removeBackgroundNoise,optimizeStreamingLatency,enableLogging; none typed/documented — and speech2speech.md:90 example usesremoveBackgroundNoisespeech2speechprovidertxt2vidduration(aliasseconds)duration→seconds, typed, txt2vid.md documents onlysecondslistModelProvidersFileSystem
movededupeNameoptionmovenewNameoptionmovecopyhas(src,dst,options,success,error)overload;movedoesn'tmovenewMetadatanewMetadata/new_metadata) + typed, undocumentedreadcacheoptionoptions.cache; absent from ReadOptions type + docsreadstatreturnWorkersoptionstatconsistencyoptionreaddirrecursiveoptionreaddirdepthoptionreaddirno_subdomainsoptionreaddirconsistency/no_thumbs/no_assocsmkdirshortcutTooptionshortcut_to), not documentedmkdirrenamealiasrenamealias ofdedupeName; doc lists only dedupeNamemkdirrecursivealiasrecursivealias ofcreateMissingParents; doc lists only createMissingParentsreaddirSubdomainsputer.fs.readdirSubdomainsbut not on FS type + undocumentedsignrevokeReadURLgetReadURLspaceputer.space()— no such top-level aliasuploaduploadwritewrite(path)overloaddatarequiredwriteFSItemuid/uuid/isDirectoryidFSItemFSItem.writedataparam typeString|File|Blob; impl + type also acceptArrayBuffer/viewKV
MAX_VALUE_SIZE399*1024(408,576 B) but documented/typed as "400 KB" in 6 siteslistKVListPagewhen any of limit/cursor/offset/includeTotal/fetchUntilFull set; type pagination overload requires limit or cursor →list({offset})typedstring[]but runtime isKVListPageaddadd({key, pathAndValueMap, optConfig}); absent from types + docsincr/decr{key, pathAndAmountMap, optConfig}; absent from types + docsget/del/flushclearclear= alias offlushin impl + types, not in KV.md/flush.mddecrKVValuestring|number|boolean|object|unknowncollapses tounknown(
MAX_KEY_SIZE= 1024 = "1 KB" is consistent.add/incr/decrdefaults, set-batch overloads, list pattern semantics, expire/expireAt, KVListPage/KVPair object docs all match.)Apps
getDeveloperProfilecreatetitleparamcreateindexURLparam'Index URL is required'getget({stats_period,…})with no name; types onlyget(name, options?)App.usersuser_email{username, user_uuid}; typeAppUseralso has optionaluser_email(
checkNameis new vs the manifest but fully wired — doc + type + list all present.)Auth
getUserasyncfn throws 401 before returning its Promise →getUser().catch()throws instead of rejecting;whoami(async) rejects correctlysignInrequest_authoptionsignInrequest_authoptiongetUser(success, error)whoamigetMonthlyUsage/getDetailedAppUsageUserobjectfeature_flags/hasDevAccountAccess/otpPerms
requestReadAppRootDirputer.permsbut neither typed nor documentedrequestWriteAppRootDirgrantUser/grantGroup/grantApp/grantAppAnyUser/grantOriginrevokeUser/revokeGroup/revokeApp/revokeAppAnyUser/revokeOrigincreateGroup/addUsersToGroup/removeUsersFromGroup/listGroupsrequestPermissionrequest; not typed, not documentedplatformsfrontmatter[apps]; method pages declare[websites, apps]Email (new module — no docs family)
sendputer.email.send), but nosrc/docs/src/Email*exists at all(No type drift: positional + options overloads and
EmailSendOptions/EmailSendResult/EmailAttachmentinterfaces align with impl.)UI (impl in UI.js / IPC.js =
src/gui/src/IPC.js)alertmessageundefinedalert()with no message → IPC gatedmessage !== undefined→ no response, promise hangs foreveralertbody_icon/icontype→ silently ignored on desktop pathpromptmessageundefinedprompt()with no message hangs forever (same IPC gate)promptoptions.defaultValueoptionsargsocialShareoptionsomittedsocialShare(url)→ IPC readsoptions.leftunguarded → TypeError, promise hangssocialSharevoid; impl returns a Promise (which never resolves — nooriginal_msg_idreply)notifytypeignoredshowNotificationnotifydurationignorednotifyuid || null; type saysPromise<string>showSpinnerhideSpinnerremoves overlaycontextMenuthemeoptionspec.theme; contextMenu.md never mentionscontextMenux/ypositionspec.x/spec.y; neither typed nor documentedsetMenubarthemeoptionspec.theme; setMenubar.md never mentionssetMenubaridfieldMenuItem.idin types + required for setMenuItemChecked/etc; docs don't documenttoggleWindowdisableMenuItemenableMenuItemsetMenuItemIcon/IconActive/CheckedrequestPermissionrequestUpgradeexitputer.ui.exit()— no such UI method; actual API isputer.exit()(exit.md is correct)contextMenu/hideWindow/showWindowsetWindowX/setWindowY/setWindowPositiontaskbar_height, not 0)on('connection')connectionis never stored in#lastBroadcastValue→ never replayslaunchApp#(as)pseudonym#(as)split in appName; undocumented in types + docslaunchAppapp_namealiasLaunchAppOptions.app_nametyped (alias forname); docs only mentionnameWorkers
getLoggingHandlegetLoggingHandleclose()unboundlogStreamObject.close = socket.close(unbound) →handle.close()throwsTypeError: Illegal invocation; siblingcancelbinds correctlygetLoggingHandlegetLoggingUrlargs droppedmakeDriverMethodcalled with noargNamesthen invoked with 2 positional args →driverArgsis{}, auth token + worker name silently discardedgetLoggingHandlelogeventexecx-puter-no-authheaderexecoptionsnot tagged(Optional); type marksinit?optionallistlist({})matches no overload (barelist()), unlike hosting'slist(options?)(
router.md/types.mddescribe the deployed-worker runtime — verified, internally consistent, not mapped to Workers.js.)Hosting
createdirPathoptionalitycreate(subdomain)alone yields{subdomain}; types declaredirPath: stringrequired + create.md marks it (required)updatedirPathoptionality + disconnectroot_dir: nullto disconnect the directory; type requires it + update.md never mentions the disconnect behaviorcreateroot_diroptionality(get/delete + Subdomain object doc match. No camelCase
rootDiralias — snake_caseroot_dironly, consistent.)Networking
Socket'drain'eventSocketEvent+ registered in EventListener but never emitted anywhere — a consumer wiringaddListener('drain')waits foreverSocketon('drain')overload'drain'but there is noon(event:'drain', …)overload (self-inconsistent type)net.generateWispV1URLputer.net, absent from Networking.md Functionsnet.fetchinput/urlparamRequestInfo|URL(incl. aRequest); docs describe first arg asurl(String) onlyTLSSocket'error'payloademit('error', e)forwards a raw caught value (may be non-Error); type/docs promiseError(
Socket/TLSSocketconstructors,write/close/addListener,open/data/error/close+tls*events all match.)Peer
serve/connectoptions.portoptions.port(server-create & client-connect), but missing fromPuterPeerOptionsand both docs'error'.errorfieldreadonly error: string, but impl always emitsnew Error(...)/ RTCError; docs treat it as an object (event.error?.message) — type is wrong, should beError/unknown'message'.datafieldArrayBuffer|string; data channel sets nobinaryType, so binary frames arrive asBlob(omitted)AppConnectionobjectObjects/AppConnection.mddocuments theputer.uiapp-connection (usesSDK/postMessage/close), unrelated topeer.d.ts— naming collision; no Objects page forPuterPeerConnection/PuterPeerServer(
ensureTurnRelays,serve/connectreturns,iceServers/forceRelay,PuterPeerServer/PuterPeerConnectionsurfaces +connection/open/closeevents all match.)OS / Utils / Drivers
printescapeHTMLoptionprint...args); types declare singletext: stringenvapp/web/gui; impl + type also yieldnodejs/web-worker/service-workerputer.on/offDrivers.call(iface, implementation, method, parameters); types declare only 2/3-arg(OS
user/versionmatch types cleanly — no OS docs family, correct per manifest.randName/appID/exitmatch.)🐛 impl-bug bucket (code fixes, not doc/type edits)
alert()/prompt()with nomessagenever get an IPC response → promise hangs forever (handlers gated onmessage !== undefined)socialShare(url)(documented minimal form) readsoptions.leftwithout guardingoptions === undefined→ TypeError, promise hangsalertbody_icon/iconnever forwarded toUIAlert— silently ignored on desktop pathnotifytypeanddurationnever forwarded toUINotification— silently ignored on desktop pathshowSpinnerearly-returns instead of ref-counting; a singlehideSpinnerremoves the overlay, contradicting the documented "until all instances hidden"test_modeoptions-object field never promoted to the wire-level flag intxt2img/txt2speech/speech2txt/speech2speech(positional boolean only);txt2vid/img2txtdo it correctlychatChatOptions.vision?typed but never read from user options (only set internally when media present) — phantom optionmovededupeNamedocumented but request body never includes a dedupe field — option is a no-opgetUser(non-async) throws the 401 before returning its Promise →getUser().catch()throws synchronously instead of rejecting (whoamihandles it correctly)getLoggingHandle().close()issocket.closecopied unbound →TypeError: Illegal invocationwhen the documented close path is usedgetLoggingHandlebuilds thegetLoggingUrldriver method with noargNames, then passes 2 positional args → both auth token and worker name are dropped from the driver call (driverArgs={})assertKeySize/assertValueSizetest.length(UTF-16 element count), not byte size; multibyte strings under-enforce, and non-string/array values haveundefined.lengthso the value-size check is skipped entirely (pre-existing, flagged as a correctness gap)🆕/🗑 manifest-drift
AI,Apps,Hosting,KV,OS,Permsmodules (each now<module>/index.js+ per-method files +lib/). Public surface mostly unchanged; the manifest's "flat.js" entries are stale.Emailmodule —modules/Email.js+types/modules/email.d.ts, wired atindex.js:292; entirely absent from the manifest, and no docs family exists.apps/getDeveloperProfile.js(undocumented),apps/checkName.js(documented),os/user.js+os/version.js(typed, no docs — OK per manifest),perms/{appRootDir,grants,groups}.js(grant/revoke/group typed-undocumented; requestRead/WriteAppRootDir untyped+undocumented).Objects/ttsengine.md,Objects/ttsvoice.md,Apps/checkName.md.