opencharm 0.0.0 → 0.2.0
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/README.md +30 -4
- package/dist/main.mjs +4242 -0
- package/package.json +52 -4
- package/sim/THIRD_PARTY_NOTICES.md +95 -0
- package/sim/charm_sim.js +2 -0
- package/sim/charm_sim.wasm +0 -0
- package/sim/index.html +68 -0
- package/sim/mic-worklet.js +22 -0
- package/sim/sim.css +148 -0
- package/sim/sim.js +412 -0
- package/LICENSE +0 -21
package/package.json
CHANGED
|
@@ -1,13 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencharm",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"opencharm": "dist/main.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/",
|
|
11
|
+
"sim/",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=24"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsdown",
|
|
19
|
+
"typecheck": "tsc --noEmit",
|
|
20
|
+
"lint": "eslint .",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@opencharm-labs/charmd": "*",
|
|
26
|
+
"@opencharm-labs/design": "*",
|
|
27
|
+
"@opencharm-labs/protocol": "*",
|
|
28
|
+
"playwright": "^1.63.0",
|
|
29
|
+
"tsdown": "^0.23.0"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"opencharm",
|
|
33
|
+
"ai-agent",
|
|
34
|
+
"hermes-agent",
|
|
35
|
+
"openclaw",
|
|
36
|
+
"claude-code",
|
|
37
|
+
"acp",
|
|
38
|
+
"mcp",
|
|
39
|
+
"esp32",
|
|
40
|
+
"esp32-s3",
|
|
41
|
+
"open-hardware",
|
|
42
|
+
"ai-companion",
|
|
43
|
+
"cli"
|
|
44
|
+
],
|
|
6
45
|
"homepage": "https://opencharm.dev",
|
|
7
46
|
"repository": {
|
|
8
47
|
"type": "git",
|
|
9
48
|
"url": "git+https://github.com/opencharm-labs/opencharm.git",
|
|
10
49
|
"directory": "packages/cli"
|
|
11
50
|
},
|
|
12
|
-
"
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/opencharm-labs/opencharm/issues"
|
|
53
|
+
},
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@agentclientprotocol/sdk": "1.5.1",
|
|
57
|
+
"opusscript": "^0.1.1",
|
|
58
|
+
"ws": "^8.22.0",
|
|
59
|
+
"zod": "^4.6.5"
|
|
60
|
+
}
|
|
13
61
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Third-party notices: the charm's WebAssembly (emulator and desktop charm)
|
|
2
|
+
|
|
3
|
+
`charm_sim.wasm` is OpenCharm OS (MIT, see `LICENSE`) compiled together with the libraries below. Their licences ask for these notices to travel with the binary.
|
|
4
|
+
|
|
5
|
+
- LVGL 9.6.0 (https://github.com/lvgl/lvgl): MIT
|
|
6
|
+
- cJSON 1.7.19 (https://github.com/DaveGamble/cJSON): MIT
|
|
7
|
+
- libopus 1.6.1 (https://github.com/xiph/opus): BSD 3-Clause
|
|
8
|
+
- Geist Mono (https://github.com/vercel/geist-font) and Noto Sans Symbols 2 (https://github.com/notofonts/symbols), converted to bitmap fonts: SIL Open Font License 1.1, texts in `firmware/core/fonts/`
|
|
9
|
+
|
|
10
|
+
## LVGL
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
MIT licence
|
|
14
|
+
Copyright (c) 2025 LVGL Kft
|
|
15
|
+
|
|
16
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## cJSON
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
|
|
27
|
+
|
|
28
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
29
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
30
|
+
in the Software without restriction, including without limitation the rights
|
|
31
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
32
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
33
|
+
furnished to do so, subject to the following conditions:
|
|
34
|
+
|
|
35
|
+
The above copyright notice and this permission notice shall be included in
|
|
36
|
+
all copies or substantial portions of the Software.
|
|
37
|
+
|
|
38
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
39
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
40
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
41
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
42
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
43
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
44
|
+
THE SOFTWARE.
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## libopus
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
Copyright 2001-2023 Xiph.Org, Skype Limited, Octasic,
|
|
52
|
+
Jean-Marc Valin, Timothy B. Terriberry,
|
|
53
|
+
CSIRO, Gregory Maxwell, Mark Borgerding,
|
|
54
|
+
Erik de Castro Lopo, Mozilla, Amazon
|
|
55
|
+
|
|
56
|
+
Redistribution and use in source and binary forms, with or without
|
|
57
|
+
modification, are permitted provided that the following conditions
|
|
58
|
+
are met:
|
|
59
|
+
|
|
60
|
+
- Redistributions of source code must retain the above copyright
|
|
61
|
+
notice, this list of conditions and the following disclaimer.
|
|
62
|
+
|
|
63
|
+
- Redistributions in binary form must reproduce the above copyright
|
|
64
|
+
notice, this list of conditions and the following disclaimer in the
|
|
65
|
+
documentation and/or other materials provided with the distribution.
|
|
66
|
+
|
|
67
|
+
- Neither the name of Internet Society, IETF or IETF Trust, nor the
|
|
68
|
+
names of specific contributors, may be used to endorse or promote
|
|
69
|
+
products derived from this software without specific prior written
|
|
70
|
+
permission.
|
|
71
|
+
|
|
72
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
73
|
+
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
74
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
75
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
|
76
|
+
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
77
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
78
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
79
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
80
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
81
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
82
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
83
|
+
|
|
84
|
+
Opus is subject to the royalty-free patent licenses which are
|
|
85
|
+
specified at:
|
|
86
|
+
|
|
87
|
+
Xiph.Org Foundation:
|
|
88
|
+
https://datatracker.ietf.org/ipr/1524/
|
|
89
|
+
|
|
90
|
+
Microsoft Corporation:
|
|
91
|
+
https://datatracker.ietf.org/ipr/1914/
|
|
92
|
+
|
|
93
|
+
Broadcom Corporation:
|
|
94
|
+
https://datatracker.ietf.org/ipr/1526/
|
|
95
|
+
```
|
package/sim/charm_sim.js
ADDED
|
@@ -0,0 +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();
|
|
2
|
+
;return Module}export default createCharm;
|
|
Binary file
|
package/sim/index.html
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>OpenCharm emulator</title>
|
|
7
|
+
<link rel="icon" href="data:," />
|
|
8
|
+
<link rel="stylesheet" href="sim.css" />
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<main>
|
|
12
|
+
<header>
|
|
13
|
+
<span class="mono">OPENCHARM · EMULATOR</span>
|
|
14
|
+
<span class="mono dim" id="status">STARTING</span>
|
|
15
|
+
</header>
|
|
16
|
+
<section class="stage">
|
|
17
|
+
<div class="charm" id="charm">
|
|
18
|
+
<canvas
|
|
19
|
+
id="screen"
|
|
20
|
+
width="480"
|
|
21
|
+
height="480"
|
|
22
|
+
aria-label="The charm's screen"
|
|
23
|
+
></canvas>
|
|
24
|
+
<button
|
|
25
|
+
class="key"
|
|
26
|
+
id="key"
|
|
27
|
+
aria-label="The key: hold to talk, press for everything else"
|
|
28
|
+
></button>
|
|
29
|
+
</div>
|
|
30
|
+
<aside>
|
|
31
|
+
<p class="mono dim">HOW TO USE</p>
|
|
32
|
+
<p>
|
|
33
|
+
<b>Hold Space</b> (or the key on the right side) to talk. A short
|
|
34
|
+
press stops speech or dismisses a line. Tap the face. Type the PIN
|
|
35
|
+
on the screen.
|
|
36
|
+
</p>
|
|
37
|
+
<p>
|
|
38
|
+
<b>Orange means it needs you.</b> On a question, hold for yes, press
|
|
39
|
+
for no.
|
|
40
|
+
</p>
|
|
41
|
+
<p>
|
|
42
|
+
<b>Mac notch</b> shows the desktop charm: its eyes beside the notch,
|
|
43
|
+
a panel that opens below it. Type the PIN on the keyboard.
|
|
44
|
+
</p>
|
|
45
|
+
<p class="mono dim">CHARMD</p>
|
|
46
|
+
<p class="mono small" id="url"></p>
|
|
47
|
+
<p class="mono dim">BUILD</p>
|
|
48
|
+
<p class="mono small" id="build"></p>
|
|
49
|
+
<div class="buttons">
|
|
50
|
+
<button class="pill" id="wifi">Drop Wi-Fi</button>
|
|
51
|
+
<button class="pill" id="shape">Round screen</button>
|
|
52
|
+
<button class="pill" id="forget">Forget pairing</button>
|
|
53
|
+
</div>
|
|
54
|
+
<p class="mono dim">LATER</p>
|
|
55
|
+
<p class="small dim">
|
|
56
|
+
Pick up, face-down, shake and battery arrive with the motion sensor
|
|
57
|
+
work (after the MVP).
|
|
58
|
+
</p>
|
|
59
|
+
<p class="small dim">
|
|
60
|
+
Same code as the charm: firmware/core compiled to WebAssembly. No
|
|
61
|
+
warranty; see README.
|
|
62
|
+
</p>
|
|
63
|
+
</aside>
|
|
64
|
+
</section>
|
|
65
|
+
</main>
|
|
66
|
+
<script type="module" src="sim.js"></script>
|
|
67
|
+
</body>
|
|
68
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Runs in the audio thread at 16 kHz: collects 60 ms (960 samples) and posts them as 16-bit PCM.
|
|
2
|
+
class MicCollector extends AudioWorkletProcessor {
|
|
3
|
+
constructor() {
|
|
4
|
+
super();
|
|
5
|
+
this.buffer = new Int16Array(960);
|
|
6
|
+
this.filled = 0;
|
|
7
|
+
}
|
|
8
|
+
process(inputs) {
|
|
9
|
+
const channel = inputs[0] && inputs[0][0];
|
|
10
|
+
if (!channel) return true;
|
|
11
|
+
for (let i = 0; i < channel.length; i++) {
|
|
12
|
+
const s = Math.max(-1, Math.min(1, channel[i]));
|
|
13
|
+
this.buffer[this.filled++] = s < 0 ? s * 0x8000 : s * 0x7fff;
|
|
14
|
+
if (this.filled === this.buffer.length) {
|
|
15
|
+
this.port.postMessage(this.buffer.slice());
|
|
16
|
+
this.filled = 0;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
registerProcessor("mic-collector", MicCollector);
|
package/sim/sim.css
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/* The OpenCharm look: a technical drawing on paper. Colour lives only on the device. */
|
|
2
|
+
:root {
|
|
3
|
+
--paper: #f6f6f4;
|
|
4
|
+
--ink: #0a0a0a;
|
|
5
|
+
--ink-2: #3a3a3a;
|
|
6
|
+
--mute: #6e6e6e;
|
|
7
|
+
--grid: rgba(10, 10, 10, 0.05);
|
|
8
|
+
--mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
9
|
+
--sans: "Geist", ui-sans-serif, system-ui, sans-serif;
|
|
10
|
+
}
|
|
11
|
+
* {
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
}
|
|
14
|
+
body {
|
|
15
|
+
margin: 0;
|
|
16
|
+
background-color: var(--paper);
|
|
17
|
+
background-image:
|
|
18
|
+
linear-gradient(var(--grid) 1px, transparent 1px),
|
|
19
|
+
linear-gradient(90deg, var(--grid) 1px, transparent 1px);
|
|
20
|
+
background-size: 24px 24px;
|
|
21
|
+
color: var(--ink);
|
|
22
|
+
font: 15px/1.5 var(--sans);
|
|
23
|
+
}
|
|
24
|
+
main {
|
|
25
|
+
max-width: 980px;
|
|
26
|
+
margin: 0 auto;
|
|
27
|
+
padding: 32px 24px;
|
|
28
|
+
}
|
|
29
|
+
header {
|
|
30
|
+
display: flex;
|
|
31
|
+
justify-content: space-between;
|
|
32
|
+
margin-bottom: 40px;
|
|
33
|
+
}
|
|
34
|
+
.mono {
|
|
35
|
+
font-family: var(--mono);
|
|
36
|
+
letter-spacing: 0.12em;
|
|
37
|
+
font-size: 12px;
|
|
38
|
+
}
|
|
39
|
+
.dim {
|
|
40
|
+
color: var(--mute);
|
|
41
|
+
}
|
|
42
|
+
.small {
|
|
43
|
+
font-size: 13px;
|
|
44
|
+
}
|
|
45
|
+
.stage {
|
|
46
|
+
display: flex;
|
|
47
|
+
gap: 56px;
|
|
48
|
+
align-items: center;
|
|
49
|
+
flex-wrap: wrap;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
}
|
|
52
|
+
.charm {
|
|
53
|
+
position: relative;
|
|
54
|
+
width: 520px;
|
|
55
|
+
padding: 20px;
|
|
56
|
+
border-radius: 13.8%;
|
|
57
|
+
background: #fff;
|
|
58
|
+
box-shadow:
|
|
59
|
+
inset 0 0 0 1px rgba(0, 0, 0, 0.18),
|
|
60
|
+
0 1px 0 rgba(0, 0, 0, 0.04);
|
|
61
|
+
}
|
|
62
|
+
.charm.round {
|
|
63
|
+
border-radius: 50%;
|
|
64
|
+
}
|
|
65
|
+
canvas {
|
|
66
|
+
display: block;
|
|
67
|
+
width: 480px;
|
|
68
|
+
height: 480px;
|
|
69
|
+
border-radius: 10.8%;
|
|
70
|
+
background: #000;
|
|
71
|
+
touch-action: none;
|
|
72
|
+
transition: filter 0.4s;
|
|
73
|
+
}
|
|
74
|
+
.charm.round canvas {
|
|
75
|
+
border-radius: 50%;
|
|
76
|
+
}
|
|
77
|
+
/* The desktop charm: a black panel under a Mac's notch, cropped to the menu-bar strip when closed. */
|
|
78
|
+
.charm.notch {
|
|
79
|
+
align-self: flex-start; /* it hangs from the top edge, like the menu bar, and only grows down */
|
|
80
|
+
padding: 0;
|
|
81
|
+
border-radius: 0 0 18px 18px;
|
|
82
|
+
background: #000;
|
|
83
|
+
height: var(--strip);
|
|
84
|
+
overflow: hidden;
|
|
85
|
+
transition: height 0.28s cubic-bezier(0.2, 0.9, 0.25, 1.1);
|
|
86
|
+
}
|
|
87
|
+
.charm.notch.open {
|
|
88
|
+
height: var(--panel);
|
|
89
|
+
}
|
|
90
|
+
.charm.notch canvas {
|
|
91
|
+
border-radius: 0;
|
|
92
|
+
}
|
|
93
|
+
.charm.notch::before {
|
|
94
|
+
/* where the Mac's notch is: the charm never draws there */
|
|
95
|
+
content: "";
|
|
96
|
+
position: absolute;
|
|
97
|
+
left: calc(50% - var(--notch) / 2);
|
|
98
|
+
top: 0;
|
|
99
|
+
width: var(--notch);
|
|
100
|
+
height: var(--strip);
|
|
101
|
+
border-radius: 0 0 10px 10px;
|
|
102
|
+
outline: 1px dashed rgba(255, 255, 255, 0.18);
|
|
103
|
+
pointer-events: none;
|
|
104
|
+
}
|
|
105
|
+
.charm.notch .key {
|
|
106
|
+
display: none;
|
|
107
|
+
}
|
|
108
|
+
.key {
|
|
109
|
+
position: absolute;
|
|
110
|
+
right: -9px;
|
|
111
|
+
top: 43%;
|
|
112
|
+
width: 9px;
|
|
113
|
+
height: 14%;
|
|
114
|
+
border: 0;
|
|
115
|
+
border-radius: 0 5px 5px 0;
|
|
116
|
+
background: #1e1f22;
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
}
|
|
119
|
+
.key.down {
|
|
120
|
+
right: -6px;
|
|
121
|
+
width: 6px;
|
|
122
|
+
}
|
|
123
|
+
aside {
|
|
124
|
+
max-width: 320px;
|
|
125
|
+
}
|
|
126
|
+
aside p {
|
|
127
|
+
margin: 0 0 14px;
|
|
128
|
+
}
|
|
129
|
+
.buttons {
|
|
130
|
+
display: flex;
|
|
131
|
+
flex-wrap: wrap;
|
|
132
|
+
gap: 8px;
|
|
133
|
+
margin: 4px 0 22px;
|
|
134
|
+
}
|
|
135
|
+
.pill {
|
|
136
|
+
font: 12px var(--mono);
|
|
137
|
+
letter-spacing: 0.08em;
|
|
138
|
+
padding: 8px 14px;
|
|
139
|
+
border-radius: 999px;
|
|
140
|
+
border: 1px solid var(--ink);
|
|
141
|
+
background: transparent;
|
|
142
|
+
color: var(--ink);
|
|
143
|
+
cursor: pointer;
|
|
144
|
+
}
|
|
145
|
+
.pill.on {
|
|
146
|
+
background: var(--ink);
|
|
147
|
+
color: var(--paper);
|
|
148
|
+
}
|