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 +1 -1
- package/dist/main.mjs +50 -18
- package/package.json +1 -1
- package/sim/charm_sim.js +1 -1
- package/sim/charm_sim.wasm +0 -0
- package/sim/sim.js +10 -1
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
|
|
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.
|
|
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
|
|
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 (
|
|
5627
|
-
|
|
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
|
-
|
|
5666
|
+
const { host, system } = localHost();
|
|
5667
|
+
const service = bonjour.publish({
|
|
5638
5668
|
name: `OpenCharm on ${computer}`,
|
|
5639
|
-
host
|
|
5669
|
+
host,
|
|
5640
5670
|
type: "opencharm",
|
|
5641
5671
|
port,
|
|
5642
5672
|
txt: { path: "/charm" }
|
|
5643
|
-
})
|
|
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 = "
|
|
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.
|
|
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;
|
package/sim/charm_sim.wasm
CHANGED
|
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
|
-
|
|
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
|
},
|