opencharm 0.13.0 → 0.14.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/README.md CHANGED
@@ -22,7 +22,7 @@ npx opencharm
22
22
  | `opencharm lock` / `unlock` / `revoke <charm>` | Lock it now; clear a block and unlock; forget it |
23
23
  | `opencharm new [charm]` | A new conversation with your agent for that charm, or every paired charm when none is named: its next turn starts a fresh session (a reply in progress finishes first). charmd also starts one after `newConversationAfterMinutes` of quiet (default 120) |
24
24
  | `opencharm replies on\|off` | The desktop charm speaks its replies, or shows them as text, from the next one; a charm on a board always speaks. To keep it, set `"speakReplies": false` in `opencharm.json` |
25
- | `opencharm look` | Show the charm's look, or change it on every unlocked charm until charmd restarts (`--colour <colour>`, `--greeting <text>`, `--motion full\|calm`); to keep it, set `charm` in `opencharm.json` |
25
+ | `opencharm look` | Show the charm's look, or change it on every unlocked charm until charmd restarts (`--colour <colour>`, `--greeting <text>`, `--motion full\|calm`, `--volume <0-100>`); to keep it, set `charm` in `opencharm.json` |
26
26
  | `opencharm dev face <charm> <state> [text]` / `dev say <charm> <text>` / `dev ask <charm> <question>` | Show a face, speak, or ask a question (hold = yes, press = no) on a connected charm (testing) |
27
27
  | `opencharm voice` / `voice install` | The local voice's models (Parakeet listening, Supertonic speaking): whether they're here, and download them now (charmd does it on its first start otherwise) |
28
28
  | `opencharm mcp` | The charm's tools for an agent (MCP over stdio): `say`, `show_face`, `ask`, `notify`, `set_look`, `new_conversation`. charmd adds it to ACP agents by itself. Other agents running as your user can add `opencharm mcp --config <file>` to their MCP settings; never an agent running as another user (it reaches charmd's admin socket) |
package/dist/main.mjs CHANGED
@@ -611,7 +611,8 @@ const look$1 = z.object({
611
611
  glyph: z.string().regex(/^#[0-9A-Fa-f]{6}$/, "a #RRGGBB colour").refine((hex) => hex.toUpperCase() !== SIGNAL_COLOUR, { message: "orange is reserved for 'it needs you'" }),
612
612
  greeting: shownText(0, 40),
613
613
  sleep_ms: z.number().int().min(0).max(864e5),
614
- motion: z.enum(["full", "calm"])
614
+ motion: z.enum(["full", "calm"]),
615
+ volume: z.number().int().min(0).max(100).optional()
615
616
  });
616
617
  const ask = z.object({
617
618
  type: z.literal("charm"),
@@ -676,7 +677,8 @@ const lookFields = {
676
677
  colour: colour.optional(),
677
678
  greeting: z.string().refine((text) => utf8Length(text) <= MAX_GREETING_BYTES, { message: `at most ${MAX_GREETING_BYTES} bytes` }).optional(),
678
679
  sleepAfterMinutes: z.number().int().min(0).max(1440).optional(),
679
- motion: z.enum(["full", "calm"]).optional()
680
+ motion: z.enum(["full", "calm"]).optional(),
681
+ volume: z.number().int().min(0).max(100).optional()
680
682
  };
681
683
  const lookUpdateSchema = z.object(lookFields).strict();
682
684
  function issues(error) {
@@ -696,7 +698,8 @@ function resolve$1(settings, defaultName) {
696
698
  glyph: glyphOf(settings.colour),
697
699
  greeting: settings.greeting ?? `Hi! I'm ${name}.`,
698
700
  sleepAfterMinutes: settings.sleepAfterMinutes,
699
- motion: settings.motion
701
+ motion: settings.motion,
702
+ volume: settings.volume
700
703
  };
701
704
  }
702
705
  function toMessage(look) {
@@ -707,7 +710,8 @@ function toMessage(look) {
707
710
  glyph: look.glyph,
708
711
  greeting: look.greeting,
709
712
  sleep_ms: look.sleepAfterMinutes * 6e4,
710
- motion: look.motion
713
+ motion: look.motion,
714
+ volume: look.volume
711
715
  };
712
716
  const checked = SERVER_SCHEMAS["charm:look"].safeParse(message);
713
717
  if (!checked.success) throw new Error(`Invalid look: ${issues(checked.error)}`);
@@ -861,6 +865,7 @@ const charmSchema = z.object({
861
865
  colour: lookFields.colour.unwrap().default("white"),
862
866
  sleepAfterMinutes: lookFields.sleepAfterMinutes.unwrap().default(4),
863
867
  motion: lookFields.motion.unwrap().default("full"),
868
+ volume: lookFields.volume.unwrap().default(85),
864
869
  agentCanChangeLook: z.boolean().default(true)
865
870
  }).strict();
866
871
  const schema = z.object({
@@ -1021,7 +1026,7 @@ function sendAdmin(socketPath, request) {
1021
1026
  }
1022
1027
  //#endregion
1023
1028
  //#region package.json
1024
- var version = "0.13.0";
1029
+ var version = "0.14.1";
1025
1030
  function channelToCube(value) {
1026
1031
  if (value < 48) return 0;
1027
1032
  if (value < 115) return 1;
@@ -1071,7 +1076,8 @@ const VALUE_FLAGS = /* @__PURE__ */ new Set([
1071
1076
  "--url",
1072
1077
  "--port",
1073
1078
  "--greeting",
1074
- "--motion"
1079
+ "--motion",
1080
+ "--volume"
1075
1081
  ]);
1076
1082
  function isPositional(args, index) {
1077
1083
  const word = args[index];
@@ -1261,10 +1267,16 @@ async function dev(ctx, args, deps) {
1261
1267
  fail$2(ctx, DEV_USAGE);
1262
1268
  }
1263
1269
  async function look(ctx, args, deps) {
1270
+ const volume = flagValue(args, "--volume");
1271
+ if (volume !== void 0 && !/^\d{1,3}$/.test(volume)) {
1272
+ fail$2(ctx, "--volume is a whole number from 0 to 100. Nothing was changed.");
1273
+ return;
1274
+ }
1264
1275
  const flags = {
1265
1276
  colour: flagValue(args, "--colour") ?? flagValue(args, "--color"),
1266
1277
  greeting: flagValue(args, "--greeting"),
1267
- motion: flagValue(args, "--motion")
1278
+ motion: flagValue(args, "--motion"),
1279
+ volume: volume === void 0 ? void 0 : Number(volume)
1268
1280
  };
1269
1281
  const update = Object.fromEntries(Object.entries(flags).filter(([, value]) => value !== void 0));
1270
1282
  const result = await deps.send(deps.socket, {
@@ -1272,7 +1284,7 @@ async function look(ctx, args, deps) {
1272
1284
  ...update
1273
1285
  });
1274
1286
  const sleep = result.sleepAfterMinutes === 0 ? "never sleeps" : `sleeps after ${result.sleepAfterMinutes} min`;
1275
- ctx.out.write(`${result.name} ${result.colour} ${result.motion} motion ${sleep}\n${result.greeting ? JSON.stringify(result.greeting) : "(no greeting)"}\n`);
1287
+ ctx.out.write(`${result.name} ${result.colour} ${result.motion} motion volume ${result.volume}% ${sleep}\n${result.greeting ? JSON.stringify(result.greeting) : "(no greeting)"}\n`);
1276
1288
  if (Object.keys(update).length === 0) return;
1277
1289
  ctx.out.write(`${result.connected === 0 ? "No charm is unlocked right now: it gets the look when it unlocks." : `Sent to ${result.connected} charm${result.connected === 1 ? "" : "s"}.`} It lasts until charmd restarts; to keep it, set "charm" in opencharm.json.\n`);
1278
1290
  }
@@ -1572,7 +1584,7 @@ function runHelp(ctx) {
1572
1584
  opencharm revoke <charm> forget a charm (it must pair again)
1573
1585
  opencharm new [charm] a new conversation with your agent (every charm when none is named)
1574
1586
  opencharm replies on|off the desktop charm speaks its replies, or shows them as text
1575
- opencharm look show or change the charm's look until charmd restarts [--colour <c|#RRGGBB>] [--greeting <text>] [--motion full|calm]
1587
+ opencharm look show or change the charm's look until charmd restarts [--colour <c|#RRGGBB>] [--greeting <text>] [--motion full|calm] [--volume <0-100>]
1576
1588
  opencharm dev face <charm> <state> [text] show a face (testing)
1577
1589
  opencharm dev say <charm> <text> speak through it (testing)
1578
1590
  opencharm dev ask <charm> <question> ask it; hold = yes, press = no (testing)
@@ -1793,7 +1805,7 @@ const TOOLS = [
1793
1805
  },
1794
1806
  {
1795
1807
  name: "set_look",
1796
- description: "Change how the charm looks when the person asks (e.g. \"make yourself blue\"): its glyph colour, its greeting after unlock, or calm motion. Lasts until charmd restarts.",
1808
+ description: "Change how the charm looks when the person asks (e.g. \"make yourself blue\"): its glyph colour, its greeting after unlock, calm motion, or how loud it speaks. Lasts until charmd restarts.",
1797
1809
  inputSchema: {
1798
1810
  type: "object",
1799
1811
  properties: {
@@ -1810,13 +1822,20 @@ const TOOLS = [
1810
1822
  type: "string",
1811
1823
  enum: ["full", "calm"],
1812
1824
  description: "calm: no glances or bounces, for less movement."
1825
+ },
1826
+ volume: {
1827
+ type: "integer",
1828
+ minimum: 0,
1829
+ maximum: 100,
1830
+ description: "The speaker's volume in percent; 0 is silent."
1813
1831
  }
1814
1832
  }
1815
1833
  },
1816
1834
  args: z.object({
1817
1835
  colour: z.string().regex(new RegExp(COLOUR)).optional(),
1818
1836
  greeting: z.string().max(40).optional(),
1819
- motion: z.enum(["full", "calm"]).optional()
1837
+ motion: z.enum(["full", "calm"]).optional(),
1838
+ volume: z.number().int().min(0).max(100).optional()
1820
1839
  }).strict(),
1821
1840
  run: async (args, send) => {
1822
1841
  const look = await send({
@@ -1824,7 +1843,7 @@ const TOOLS = [
1824
1843
  ...args,
1825
1844
  source: "agent"
1826
1845
  });
1827
- return `The charm is now ${look.colour}, ${look.motion} motion, greeting "${look.greeting}".`;
1846
+ return `The charm is now ${look.colour}, ${look.motion} motion, volume ${look.volume}%, greeting "${look.greeting}".`;
1828
1847
  }
1829
1848
  },
1830
1849
  {
@@ -5623,8 +5642,18 @@ function localHost() {
5623
5642
  } catch {
5624
5643
  label = "";
5625
5644
  }
5626
- if (!/^[A-Za-z0-9-]{1,63}$/.test(label)) label = hostname().split(".")[0] ?? "";
5627
- return `${label}.local`;
5645
+ if (/^[A-Za-z0-9-]{1,63}$/.test(label)) return {
5646
+ host: `${label}.local`,
5647
+ system: true
5648
+ };
5649
+ return {
5650
+ host: `${hostname().split(".")[0] ?? ""}.local`,
5651
+ system: false
5652
+ };
5653
+ }
5654
+ function leaveHostToSystem(service) {
5655
+ const records = service.records.bind(service);
5656
+ service.records = () => records().filter((record) => record.type !== "A" && record.type !== "AAAA");
5628
5657
  }
5629
5658
  const announceOnNetwork = (port) => {
5630
5659
  let warned = false;
@@ -5634,13 +5663,16 @@ const announceOnNetwork = (port) => {
5634
5663
  console.error(`charmd: mDNS: ${String(error)}`);
5635
5664
  });
5636
5665
  const computer = hostname().replace(/\.local$/, "");
5637
- bonjour.publish({
5666
+ const { host, system } = localHost();
5667
+ const service = bonjour.publish({
5638
5668
  name: `OpenCharm on ${computer}`,
5639
- host: localHost(),
5669
+ host,
5640
5670
  type: "opencharm",
5641
5671
  port,
5642
5672
  txt: { path: "/charm" }
5643
- }).on("error", (error) => {
5673
+ });
5674
+ if (system) leaveHostToSystem(service);
5675
+ service.on("error", (error) => {
5644
5676
  console.error(`charmd: couldn't announce itself on the network: ${String(error)}`);
5645
5677
  });
5646
5678
  return () => new Promise((resolve) => {
@@ -8172,7 +8204,7 @@ function formatIdentity(unit, version, commit) {
8172
8204
  let cached;
8173
8205
  function identity() {
8174
8206
  if (cached) return cached;
8175
- const commit = "b5f9085";
8207
+ const commit = "8c79a0d";
8176
8208
  cached = {
8177
8209
  version: `cli@${version}`,
8178
8210
  commit,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencharm",
3
- "version": "0.13.0",
3
+ "version": "0.14.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,height){charmSim.panel(!!open,height)}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((globalThis.charmStore||"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((globalThis.charmStore||"opencharm.")+UTF8ToString(key),UTF8ToString(value))}function js_store_erase(key){localStorage.removeItem((globalThis.charmStore||"opencharm.")+UTF8ToString(key))}function js_reconnect(){charmSim.reconnect()}var _sim_set_build,_sim_commit,_sim_panel_radius,_sim_init,_sim_frame,_sim_pin_key,_sim_pin_ready,_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,_sim_status_open,_sim_status,_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_panel_radius=Module["_sim_panel_radius"]=wasmExports["u"];_sim_init=Module["_sim_init"]=wasmExports["v"];_sim_frame=Module["_sim_frame"]=wasmExports["w"];_sim_pin_key=Module["_sim_pin_key"]=wasmExports["x"];_sim_pin_ready=Module["_sim_pin_ready"]=wasmExports["y"];_sim_frame_box=Module["_sim_frame_box"]=wasmExports["z"];_sim_frame_dirty=Module["_sim_frame_dirty"]=wasmExports["A"];_sim_tick=Module["_sim_tick"]=wasmExports["B"];_sim_pointer=Module["_sim_pointer"]=wasmExports["C"];_sim_key=Module["_sim_key"]=wasmExports["D"];_sim_connected=Module["_sim_connected"]=wasmExports["E"];_sim_disconnected=Module["_sim_disconnected"]=wasmExports["F"];_sim_text=Module["_sim_text"]=wasmExports["G"];_sim_type=Module["_sim_type"]=wasmExports["H"];_sim_audio=Module["_sim_audio"]=wasmExports["I"];_sim_mic_pcm=Module["_sim_mic_pcm"]=wasmExports["J"];_sim_token=Module["_sim_token"]=wasmExports["K"];_sim_forget=Module["_sim_forget"]=wasmExports["L"];_sim_status_open=Module["_sim_status_open"]=wasmExports["M"];_sim_status=Module["_sim_status"]=wasmExports["N"];_free=Module["_free"]=wasmExports["O"];_malloc=Module["_malloc"]=wasmExports["P"];memory=wasmMemory=wasmExports["q"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={c:___cxa_throw,m:__abort_js,k:_emscripten_resize_heap,l:_fd_close,b:_fd_write,f:js_brightness,a:js_mic,p:js_panel,h:js_play_pcm,n:js_reconnect,i:js_send_binary,j:js_send_text,g:js_stop_audio,o:js_store_erase,e:js_store_get,d: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["s"]()}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,height){charmSim.panel(!!open,height)}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_volume(percent){charmSim.volume(percent)}function js_store_get(key){var value=localStorage.getItem((globalThis.charmStore||"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((globalThis.charmStore||"opencharm.")+UTF8ToString(key),UTF8ToString(value))}function js_store_erase(key){localStorage.removeItem((globalThis.charmStore||"opencharm.")+UTF8ToString(key))}function js_reconnect(){charmSim.reconnect()}var _sim_set_build,_sim_commit,_sim_panel_radius,_sim_init,_sim_frame,_sim_pin_key,_sim_pin_ready,_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,_sim_status_open,_sim_status,_free,_malloc,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_sim_set_build=Module["_sim_set_build"]=wasmExports["t"];_sim_commit=Module["_sim_commit"]=wasmExports["u"];_sim_panel_radius=Module["_sim_panel_radius"]=wasmExports["v"];_sim_init=Module["_sim_init"]=wasmExports["w"];_sim_frame=Module["_sim_frame"]=wasmExports["x"];_sim_pin_key=Module["_sim_pin_key"]=wasmExports["y"];_sim_pin_ready=Module["_sim_pin_ready"]=wasmExports["z"];_sim_frame_box=Module["_sim_frame_box"]=wasmExports["A"];_sim_frame_dirty=Module["_sim_frame_dirty"]=wasmExports["B"];_sim_tick=Module["_sim_tick"]=wasmExports["C"];_sim_pointer=Module["_sim_pointer"]=wasmExports["D"];_sim_key=Module["_sim_key"]=wasmExports["E"];_sim_connected=Module["_sim_connected"]=wasmExports["F"];_sim_disconnected=Module["_sim_disconnected"]=wasmExports["G"];_sim_text=Module["_sim_text"]=wasmExports["H"];_sim_type=Module["_sim_type"]=wasmExports["I"];_sim_audio=Module["_sim_audio"]=wasmExports["J"];_sim_mic_pcm=Module["_sim_mic_pcm"]=wasmExports["K"];_sim_token=Module["_sim_token"]=wasmExports["L"];_sim_forget=Module["_sim_forget"]=wasmExports["M"];_sim_status_open=Module["_sim_status_open"]=wasmExports["N"];_sim_status=Module["_sim_status"]=wasmExports["O"];_free=Module["_free"]=wasmExports["P"];_malloc=Module["_malloc"]=wasmExports["Q"];memory=wasmMemory=wasmExports["r"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={c:___cxa_throw,m:__abort_js,k:_emscripten_resize_heap,l:_fd_close,b:_fd_write,f:js_brightness,a:js_mic,q:js_panel,h:js_play_pcm,n:js_reconnect,i:js_send_binary,j:js_send_text,g:js_stop_audio,o:js_store_erase,d:js_store_get,p:js_store_set,e:js_volume};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
@@ -65,6 +65,8 @@ let firstFrame = false;
65
65
  let mic; // {stream, ctx} while the key is held; the microphone is closed otherwise
66
66
  let playCtx;
67
67
  let playAt = 0;
68
+ let speaker; // the gain every reply plays through: the look's volume (spec 014)
69
+ let volume = 0.85;
68
70
  const playing = new Set();
69
71
 
70
72
  window.charmSim = {
@@ -86,7 +88,8 @@ window.charmSim = {
86
88
  buffer.copyToChannel(pcm, 0);
87
89
  const source = playCtx.createBufferSource();
88
90
  source.buffer = buffer;
89
- source.connect(playCtx.destination);
91
+ speaker ??= new GainNode(playCtx, { gain: volume });
92
+ source.connect(speaker).connect(playCtx.destination);
90
93
  playAt = Math.max(playAt, playCtx.currentTime + 0.05);
91
94
  source.start(playAt);
92
95
  playAt += buffer.duration;
@@ -102,6 +105,12 @@ window.charmSim = {
102
105
  playing.clear();
103
106
  playAt = 0;
104
107
  },
108
+ volume: (percent) => {
109
+ volume = Math.min(Math.max(percent, 0), 100) / 100;
110
+ // Eased over a few milliseconds: a jump mid-reply clicks.
111
+ if (speaker && playCtx)
112
+ speaker.gain.setTargetAtTime(volume, playCtx.currentTime, 0.02);
113
+ },
105
114
  brightness: (percent) => {
106
115
  canvas.style.filter = `brightness(${Math.max(percent, 25) / 100})`;
107
116
  },