opencharm 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.mjs CHANGED
@@ -497,6 +497,11 @@ const unlock = z.object({
497
497
  op: z.literal("unlock"),
498
498
  pin: z.string().regex(/^\d{4,12}$/)
499
499
  });
500
+ const typed = z.object({
501
+ type: z.literal("charm"),
502
+ op: z.literal("text"),
503
+ text: z.string().max(2e3).refine((text) => text.trim().length > 0, { message: "empty" })
504
+ });
500
505
  const answer = z.object({
501
506
  type: z.literal("charm"),
502
507
  op: z.literal("answer"),
@@ -507,7 +512,8 @@ const serverHello = z.object({
507
512
  type: z.literal("hello"),
508
513
  transport: z.literal("websocket"),
509
514
  session_id: z.string().min(1).max(128),
510
- audio_params: audioParams
515
+ audio_params: audioParams,
516
+ features: z.object({ text: z.boolean().optional() }).optional()
511
517
  });
512
518
  const stt = z.object({
513
519
  session_id: sessionId,
@@ -593,7 +599,8 @@ const CLIENT_SCHEMAS = {
593
599
  listen,
594
600
  abort,
595
601
  "charm:unlock": unlock,
596
- "charm:answer": answer
602
+ "charm:answer": answer,
603
+ "charm:text": typed
597
604
  };
598
605
  const SERVER_SCHEMAS = {
599
606
  hello: serverHello,
@@ -962,7 +969,7 @@ function sendAdmin(socketPath, request) {
962
969
  }
963
970
  //#endregion
964
971
  //#region package.json
965
- var version = "0.5.0";
972
+ var version = "0.6.1";
966
973
  function channelToCube(value) {
967
974
  if (value < 48) return 0;
968
975
  if (value < 115) return 1;
@@ -2353,7 +2360,8 @@ var Session = class {
2353
2360
  type: "hello",
2354
2361
  transport: "websocket",
2355
2362
  session_id: this.sessionId,
2356
- audio_params: AUDIO_DOWN
2363
+ audio_params: AUDIO_DOWN,
2364
+ ...this.#build?.kind === "desktop" ? { features: { text: true } } : {}
2357
2365
  });
2358
2366
  const { token } = this.#deps;
2359
2367
  if (token === void 0) {
@@ -2398,6 +2406,9 @@ var Session = class {
2398
2406
  if (message.state === "start") turn.listenStart();
2399
2407
  else if (message.state === "stop") turn.listenStop();
2400
2408
  } else if (message.type === "abort") turn.abort();
2409
+ else if (message.type === "charm" && message.op === "text") {
2410
+ if (this.#build?.kind === "desktop") turn.typed(message.text);
2411
+ }
2401
2412
  }
2402
2413
  async #onUnlock(pin) {
2403
2414
  if (this.#state === "unlocked") {
@@ -3289,6 +3300,7 @@ function cleanForSpeech(text) {
3289
3300
  //#endregion
3290
3301
  //#region ../charmd/src/turn/turn.ts
3291
3302
  const MAX_FRAMES = 1e3;
3303
+ const LEAD_MS = 250;
3292
3304
  const READING_WORDS_PER_SECOND = 3;
3293
3305
  const MIN_READING_MS = 2e3;
3294
3306
  const LOOKAHEAD = 2;
@@ -3321,6 +3333,7 @@ var TurnController = class {
3321
3333
  #generation = 0;
3322
3334
  #controller;
3323
3335
  #speaking = false;
3336
+ #playedBy = 0;
3324
3337
  #asking;
3325
3338
  #endFace;
3326
3339
  #deps;
@@ -3392,9 +3405,13 @@ var TurnController = class {
3392
3405
  this.#face("idle");
3393
3406
  return Promise.resolve();
3394
3407
  }
3395
- return this.#run(frames);
3408
+ return this.#run({ frames });
3396
3409
  }
3397
- async #run(frames) {
3410
+ typed(text) {
3411
+ if (this.busy) this.#cancel();
3412
+ return this.#run({ text: text.trim() });
3413
+ }
3414
+ async #run(input) {
3398
3415
  const generation = ++this.#generation;
3399
3416
  const live = () => this.#generation === generation;
3400
3417
  const controller = new AbortController();
@@ -3439,14 +3456,17 @@ var TurnController = class {
3439
3456
  this.#state = "thinking";
3440
3457
  this.#face("thinking");
3441
3458
  try {
3442
- const ogg = writeOggOpus(frames, { inputSampleRate: 16e3 });
3443
- heard = await Promise.race([inStage("stt", () => this.#deps.voice.transcribe(ogg, signal)), timeoutGuard]);
3444
- timings.sttMs = now() - started;
3445
- if (!live()) return;
3446
- this.#deps.send({
3447
- type: "stt",
3448
- text: heard
3449
- });
3459
+ if ("text" in input) heard = input.text;
3460
+ else {
3461
+ const ogg = writeOggOpus(input.frames, { inputSampleRate: 16e3 });
3462
+ heard = await Promise.race([inStage("stt", () => this.#deps.voice.transcribe(ogg, signal)), timeoutGuard]);
3463
+ timings.sttMs = now() - started;
3464
+ if (!live()) return;
3465
+ this.#deps.send({
3466
+ type: "stt",
3467
+ text: heard
3468
+ });
3469
+ }
3450
3470
  if (!heard) {
3451
3471
  outcome = "nothing-heard";
3452
3472
  this.#state = "idle";
@@ -3474,7 +3494,7 @@ var TurnController = class {
3474
3494
  signal,
3475
3495
  ...ask ? { ask: (action) => this.ask(`${label} wants to ${action}.`) } : {}
3476
3496
  });
3477
- said = await this.#speak(reply, signal, live, timings, started, timeoutGuard, guessLanguage(heard) ?? this.#deps.voice.language ?? "en", clock, this.#deps.speakAloud?.() ?? true);
3497
+ said = await this.#speak(reply, signal, live, timings, started, timeoutGuard, guessLanguage(heard) ?? this.#deps.voice.language ?? "en", clock, !("text" in input) && (this.#deps.speakAloud?.() ?? true));
3478
3498
  if (!live()) return;
3479
3499
  if (this.#speaking) this.#deps.send({
3480
3500
  type: "tts",
@@ -3509,6 +3529,7 @@ var TurnController = class {
3509
3529
  outcome: live() ? outcome : "cancelled",
3510
3530
  ...timings,
3511
3531
  totalMs: now() - started,
3532
+ ..."text" in input ? { typed: true } : {},
3512
3533
  ...this.#deps.logTranscripts ? {
3513
3534
  heard,
3514
3535
  said: said.trim()
@@ -3582,6 +3603,7 @@ var TurnController = class {
3582
3603
  if (!this.#speaking) {
3583
3604
  this.#speaking = true;
3584
3605
  this.#state = "speaking";
3606
+ this.#playedBy = 0;
3585
3607
  timings.firstAudioMs = now() - started;
3586
3608
  clock.started();
3587
3609
  this.#deps.send({
@@ -3690,12 +3712,14 @@ var TurnController = class {
3690
3712
  }
3691
3713
  async #sendPaced(packets, live, onPacket) {
3692
3714
  const sleep = this.#deps.sleep ?? sleepMs;
3693
- let index = 0;
3715
+ const now = this.#deps.now ?? Date.now;
3694
3716
  for await (const packet of packets) {
3695
3717
  if (!live()) return;
3696
3718
  this.#deps.sendAudio(packet);
3697
3719
  onPacket();
3698
- if (index++ >= 2) await sleep(opusPacketSamples48k(packet) / 48);
3720
+ this.#playedBy = Math.max(this.#playedBy, now()) + opusPacketSamples48k(packet) / 48;
3721
+ const wait = this.#playedBy - LEAD_MS - now();
3722
+ if (wait > 0) await sleep(wait);
3699
3723
  }
3700
3724
  }
3701
3725
  dispose() {
@@ -4917,7 +4941,7 @@ function formatIdentity(unit, version, commit) {
4917
4941
  let cached;
4918
4942
  function identity() {
4919
4943
  if (cached) return cached;
4920
- const commit = "385e52a";
4944
+ const commit = "e5ffe6e";
4921
4945
  cached = {
4922
4946
  version: `cli@${version}`,
4923
4947
  commit,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencharm",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "An open-source body for the AI agent you already run: a face, a voice, one key. Claude Code, Codex, Gemini CLI, Hermes, OpenClaw or any ACP or OpenAI-compatible agent; on a small board, your Mac's notch or the browser.",
5
5
  "type": "module",
6
6
  "bin": {
package/sim/charm_sim.js CHANGED
@@ -1,2 +1,2 @@
1
- async function createCharm(moduleArg={}){var Module=moduleArg;var ENVIRONMENT_IS_WEB=true;var ENVIRONMENT_IS_WORKER=false;var programArgs=[];var thisProgram="./this.program";var _scriptName=import.meta.url;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;class EmscriptenEH{}class EmscriptenSjLj extends EmscriptenEH{}var runtimeInitialized=false;function getMemoryBuffer(){return wasmMemory.buffer}function updateMemoryViews(){if(HEAP8?.buffer?.resizable)return;var b=getMemoryBuffer();HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b)}function preRun(){var preRun=Module["preRun"];if(preRun){if(typeof preRun=="function")preRun=[preRun];onPreRuns.push(...preRun)}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["r"]()}function postRun(){var postRun=Module["postRun"];if(postRun){if(typeof postRun=="function")postRun=[postRun];onPostRuns.push(...postRun)}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what=`Aborted(${what})`;err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);throw e}var wasmBinaryFile;function findWasmBinary(){if(Module["locateFile"]){return locateFile("charm_sim.wasm")}return new URL("charm_sim.wasm",import.meta.url).href}function getBinarySync(file){if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();var instantiateWasm=Module["instantiateWasm"];if(instantiateWasm){return new Promise(resolve=>{instantiateWasm(info,inst=>resolve(receiveInstance(inst)))})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP8;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var onPreRuns=[];var noExitRuntime=true;var HEAPU32;class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var uncaughtExceptionCount=0;var __Unwind_RaiseException=ex=>{abort()};var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);uncaughtExceptionCount++;__Unwind_RaiseException(ptr)};var __abort_js=()=>abort("");var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var HEAPU8;var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var _fd_close=fd=>52;var printCharBuffers=[null,[],[]];var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx<endPtr){var u0=heapOrArray[idx++];if(!(u0&128)){str+=String.fromCharCode(u0);continue}var u1=heapOrArray[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}var u2=heapOrArray[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u0=(u0&7)<<18|u1<<12|u2<<6|heapOrArray[idx++]&63}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}}return str};var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(!curr||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i<iovcnt;i++){var ptr=HEAPU32[iov>>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j<len;j++){printChar(fd,HEAPU8[ptr+j])}num+=len}HEAPU32[pnum>>2]=num;return 0};var HEAPF32;var HEAP16;var HEAP32;var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i<str.length;++i){var u=str.codePointAt(i);if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var lengthBytesUTF8=str=>{var len=0;for(var i=0;i<str.length;++i){var c=str.charCodeAt(i);if(c<=127){len++}else if(c<=2047){len+=2}else if(c>=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["arguments"])programArgs=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];var preInit=Module["preInit"];if(preInit){if(typeof preInit=="function")Module["preInit"]=preInit=[preInit];while(preInit.length>0){preInit.shift()()}}}Module["UTF8ToString"]=UTF8ToString;Module["stringToUTF8"]=stringToUTF8;Module["lengthBytesUTF8"]=lengthBytesUTF8;function js_panel(open){charmSim.panel(!!open)}function js_send_text(json){charmSim.sendText(UTF8ToString(json))}function js_send_binary(data,size){charmSim.sendBinary(HEAPU8.slice(data,data+size))}function js_mic(on){charmSim.mic(!!on)}function js_play_pcm(pcm,samples){charmSim.playPcm(HEAPF32.slice(pcm>>2,(pcm>>2)+samples))}function js_stop_audio(){charmSim.stopAudio()}function js_brightness(percent){charmSim.brightness(percent)}function js_store_get(key){var value=localStorage.getItem("opencharm."+UTF8ToString(key));if(value===null)return 0;var size=lengthBytesUTF8(value)+1;var ptr=_malloc(size);stringToUTF8(value,ptr,size);return ptr}function js_store_set(key,value){localStorage.setItem("opencharm."+UTF8ToString(key),UTF8ToString(value))}function js_store_erase(key){localStorage.removeItem("opencharm."+UTF8ToString(key))}function js_reconnect(){charmSim.reconnect()}var _sim_set_build,_sim_commit,_sim_init,_sim_frame,_sim_pin_key,_sim_frame_box,_sim_frame_dirty,_sim_tick,_sim_pointer,_sim_key,_sim_connected,_sim_disconnected,_sim_text,_sim_audio,_sim_mic_pcm,_sim_token,_sim_forget,_free,_malloc,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_sim_set_build=Module["_sim_set_build"]=wasmExports["s"];_sim_commit=Module["_sim_commit"]=wasmExports["t"];_sim_init=Module["_sim_init"]=wasmExports["u"];_sim_frame=Module["_sim_frame"]=wasmExports["v"];_sim_pin_key=Module["_sim_pin_key"]=wasmExports["w"];_sim_frame_box=Module["_sim_frame_box"]=wasmExports["x"];_sim_frame_dirty=Module["_sim_frame_dirty"]=wasmExports["y"];_sim_tick=Module["_sim_tick"]=wasmExports["z"];_sim_pointer=Module["_sim_pointer"]=wasmExports["A"];_sim_key=Module["_sim_key"]=wasmExports["B"];_sim_connected=Module["_sim_connected"]=wasmExports["C"];_sim_disconnected=Module["_sim_disconnected"]=wasmExports["D"];_sim_text=Module["_sim_text"]=wasmExports["E"];_sim_audio=Module["_sim_audio"]=wasmExports["F"];_sim_mic_pcm=Module["_sim_mic_pcm"]=wasmExports["G"];_sim_token=Module["_sim_token"]=wasmExports["H"];_sim_forget=Module["_sim_forget"]=wasmExports["I"];_free=Module["_free"]=wasmExports["J"];_malloc=Module["_malloc"]=wasmExports["K"];memory=wasmMemory=wasmExports["q"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={o:___cxa_throw,l:__abort_js,j:_emscripten_resize_heap,k:_fd_close,b:_fd_write,e:js_brightness,a:js_mic,p:js_panel,g:js_play_pcm,m:js_reconnect,h:js_send_binary,i:js_send_text,f:js_stop_audio,n:js_store_erase,d:js_store_get,c:js_store_set};async function run(){preRun();var setStatus=Module["setStatus"];if(setStatus){setStatus("Running...");await new Promise(resolve=>setTimeout(resolve,1));setTimeout(setStatus,1,"")}if(ABORT)return;initRuntime();Module["onRuntimeInitialized"]?.();postRun()}var wasmExports;wasmExports=await createWasm();await run();
1
+ async function createCharm(moduleArg={}){var Module=moduleArg;var ENVIRONMENT_IS_WEB=true;var ENVIRONMENT_IS_WORKER=false;var programArgs=[];var thisProgram="./this.program";var _scriptName=import.meta.url;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;class EmscriptenEH{}class EmscriptenSjLj extends EmscriptenEH{}var runtimeInitialized=false;function getMemoryBuffer(){return wasmMemory.buffer}function updateMemoryViews(){if(HEAP8?.buffer?.resizable)return;var b=getMemoryBuffer();HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b)}function preRun(){var preRun=Module["preRun"];if(preRun){if(typeof preRun=="function")preRun=[preRun];onPreRuns.push(...preRun)}callRuntimeCallbacks(onPreRuns)}function initRuntime(){runtimeInitialized=true;wasmExports["r"]()}function postRun(){var postRun=Module["postRun"];if(postRun){if(typeof postRun=="function")postRun=[postRun];onPostRuns.push(...postRun)}callRuntimeCallbacks(onPostRuns)}function abort(what){Module["onAbort"]?.(what);what=`Aborted(${what})`;err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);throw e}var wasmBinaryFile;function findWasmBinary(){if(Module["locateFile"]){return locateFile("charm_sim.wasm")}return new URL("charm_sim.wasm",import.meta.url).href}function getBinarySync(file){if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();var instantiateWasm=Module["instantiateWasm"];if(instantiateWasm){return new Promise(resolve=>{instantiateWasm(info,inst=>resolve(receiveInstance(inst)))})}wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP8;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var onPostRuns=[];var onPreRuns=[];var noExitRuntime=true;var HEAPU32;class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){HEAPU32[this.ptr+4>>2]=type}get_type(){return HEAPU32[this.ptr+4>>2]}set_destructor(destructor){HEAPU32[this.ptr+8>>2]=destructor}get_destructor(){return HEAPU32[this.ptr+8>>2]}set_caught(caught){caught=caught?1:0;HEAP8[this.ptr+12]=caught}get_caught(){return HEAP8[this.ptr+12]!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+13]=rethrown}get_rethrown(){return HEAP8[this.ptr+13]!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){HEAPU32[this.ptr+16>>2]=adjustedPtr}get_adjusted_ptr(){return HEAPU32[this.ptr+16>>2]}}var uncaughtExceptionCount=0;var __Unwind_RaiseException=ex=>{abort()};var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);uncaughtExceptionCount++;__Unwind_RaiseException(ptr)};var __abort_js=()=>abort("");var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var HEAPU8;var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var _fd_close=fd=>52;var printCharBuffers=[null,[],[]];var UTF8Decoder=globalThis.TextDecoder&&new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead,ignoreNul)=>{var endPtr=findStringEnd(heapOrArray,idx,maxBytesToRead,ignoreNul);if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx<endPtr){var u0=heapOrArray[idx++];if(!(u0&128)){str+=String.fromCharCode(u0);continue}var u1=heapOrArray[idx++]&63;if((u0&224)==192){str+=String.fromCharCode((u0&31)<<6|u1);continue}var u2=heapOrArray[idx++]&63;if((u0&240)==224){u0=(u0&15)<<12|u1<<6|u2}else{u0=(u0&7)<<18|u1<<12|u2<<6|heapOrArray[idx++]&63}if(u0<65536){str+=String.fromCharCode(u0)}else{var ch=u0-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}}return str};var printChar=(stream,curr)=>{var buffer=printCharBuffers[stream];if(!curr||curr===10){(stream===1?out:err)(UTF8ArrayToString(buffer));buffer.length=0}else{buffer.push(curr)}};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead,ignoreNul):"";var _fd_write=(fd,iov,iovcnt,pnum)=>{var num=0;for(var i=0;i<iovcnt;i++){var ptr=HEAPU32[iov>>2];var len=HEAPU32[iov+4>>2];iov+=8;for(var j=0;j<len;j++){printChar(fd,HEAPU8[ptr+j])}num+=len}HEAPU32[pnum>>2]=num;return 0};var HEAPF32;var HEAP16;var HEAP32;var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i<str.length;++i){var u=str.codePointAt(i);if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63;i++}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var lengthBytesUTF8=str=>{var len=0;for(var i=0;i<str.length;++i){var c=str.charCodeAt(i);if(c<=127){len++}else if(c<=2047){len+=2}else if(c>=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};{if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(Module["print"])out=Module["print"];if(Module["printErr"])err=Module["printErr"];if(Module["arguments"])programArgs=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];var preInit=Module["preInit"];if(preInit){if(typeof preInit=="function")Module["preInit"]=preInit=[preInit];while(preInit.length>0){preInit.shift()()}}}Module["UTF8ToString"]=UTF8ToString;Module["stringToUTF8"]=stringToUTF8;Module["lengthBytesUTF8"]=lengthBytesUTF8;function js_panel(open){charmSim.panel(!!open)}function js_send_text(json){charmSim.sendText(UTF8ToString(json))}function js_send_binary(data,size){charmSim.sendBinary(HEAPU8.slice(data,data+size))}function js_mic(on){charmSim.mic(!!on)}function js_play_pcm(pcm,samples){charmSim.playPcm(HEAPF32.slice(pcm>>2,(pcm>>2)+samples))}function js_stop_audio(){charmSim.stopAudio()}function js_brightness(percent){charmSim.brightness(percent)}function js_store_get(key){var value=localStorage.getItem("opencharm."+UTF8ToString(key));if(value===null)return 0;var size=lengthBytesUTF8(value)+1;var ptr=_malloc(size);stringToUTF8(value,ptr,size);return ptr}function js_store_set(key,value){localStorage.setItem("opencharm."+UTF8ToString(key),UTF8ToString(value))}function js_store_erase(key){localStorage.removeItem("opencharm."+UTF8ToString(key))}function js_reconnect(){charmSim.reconnect()}var _sim_set_build,_sim_commit,_sim_init,_sim_frame,_sim_pin_key,_sim_frame_box,_sim_frame_dirty,_sim_tick,_sim_pointer,_sim_key,_sim_connected,_sim_disconnected,_sim_text,_sim_type,_sim_audio,_sim_mic_pcm,_sim_token,_sim_forget,_free,_malloc,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_sim_set_build=Module["_sim_set_build"]=wasmExports["s"];_sim_commit=Module["_sim_commit"]=wasmExports["t"];_sim_init=Module["_sim_init"]=wasmExports["u"];_sim_frame=Module["_sim_frame"]=wasmExports["v"];_sim_pin_key=Module["_sim_pin_key"]=wasmExports["w"];_sim_frame_box=Module["_sim_frame_box"]=wasmExports["x"];_sim_frame_dirty=Module["_sim_frame_dirty"]=wasmExports["y"];_sim_tick=Module["_sim_tick"]=wasmExports["z"];_sim_pointer=Module["_sim_pointer"]=wasmExports["A"];_sim_key=Module["_sim_key"]=wasmExports["B"];_sim_connected=Module["_sim_connected"]=wasmExports["C"];_sim_disconnected=Module["_sim_disconnected"]=wasmExports["D"];_sim_text=Module["_sim_text"]=wasmExports["E"];_sim_type=Module["_sim_type"]=wasmExports["F"];_sim_audio=Module["_sim_audio"]=wasmExports["G"];_sim_mic_pcm=Module["_sim_mic_pcm"]=wasmExports["H"];_sim_token=Module["_sim_token"]=wasmExports["I"];_sim_forget=Module["_sim_forget"]=wasmExports["J"];_free=Module["_free"]=wasmExports["K"];_malloc=Module["_malloc"]=wasmExports["L"];memory=wasmMemory=wasmExports["q"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={o:___cxa_throw,l:__abort_js,j:_emscripten_resize_heap,k:_fd_close,b:_fd_write,e:js_brightness,a:js_mic,p:js_panel,g:js_play_pcm,m:js_reconnect,h:js_send_binary,i:js_send_text,f:js_stop_audio,n:js_store_erase,d:js_store_get,c:js_store_set};async function run(){preRun();var setStatus=Module["setStatus"];if(setStatus){setStatus("Running...");await new Promise(resolve=>setTimeout(resolve,1));setTimeout(setStatus,1,"")}if(ABORT)return;initRuntime();Module["onRuntimeInitialized"]?.();postRun()}var wasmExports;wasmExports=await createWasm();await run();
2
2
  ;return Module}export default createCharm;
Binary file
package/sim/sim.js CHANGED
@@ -109,6 +109,14 @@ window.charmSim = {
109
109
  M._sim_forget();
110
110
  ws?.close();
111
111
  },
112
+ // Typed text from the desktop charm's field (spec 013): "sent", "busy" (a question or a talk), or
113
+ // "unsupported" (an older charmd).
114
+ type: (text) => {
115
+ let result = 0;
116
+ withString(text, (ptr) => (result = M._sim_type(ptr)));
117
+ wake();
118
+ return ["busy", "sent", "unsupported"][result] ?? "busy";
119
+ },
112
120
  // A PIN key from a keyboard or the desktop app ("0"…"9", "<", "OK").
113
121
  pinKey: (key) => {
114
122
  withString(key, (ptr) => M._sim_pin_key(ptr));
@@ -291,15 +299,22 @@ function key(down) {
291
299
  wake();
292
300
  }
293
301
 
302
+ // Keys typed into a text field (the desktop charm's typing field) are text, not the charm's key or PIN.
303
+ const typingInto = (e) =>
304
+ e.target instanceof HTMLElement &&
305
+ (e.target.isContentEditable ||
306
+ e.target.tagName === "INPUT" ||
307
+ e.target.tagName === "TEXTAREA");
308
+
294
309
  let spaceDown = false;
295
310
  addEventListener("keydown", (e) => {
296
- if (e.code !== "Space" || e.repeat || spaceDown) return;
311
+ if (e.code !== "Space" || e.repeat || spaceDown || typingInto(e)) return;
297
312
  e.preventDefault();
298
313
  spaceDown = true;
299
314
  key(true);
300
315
  });
301
316
  addEventListener("keyup", (e) => {
302
- if (e.code !== "Space") return;
317
+ if (e.code !== "Space" || !spaceDown) return;
303
318
  e.preventDefault();
304
319
  spaceDown = false;
305
320
  key(false);
@@ -349,6 +364,7 @@ document.getElementById("shape").addEventListener("click", () => {
349
364
 
350
365
  // The PIN can be typed on the keyboard too (the notch has no room for a pad).
351
366
  addEventListener("keydown", (e) => {
367
+ if (typingInto(e)) return;
352
368
  const key = /^[0-9]$/.test(e.key)
353
369
  ? e.key
354
370
  : e.key === "Backspace"