opencharm 0.9.0 → 0.11.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 CHANGED
@@ -18,7 +18,7 @@ npx opencharm
18
18
  | `opencharm sim` | The emulator (OpenCharm OS in a browser tab, for developing the charm), talking to charmd (`--url`, `--port`, `--no-open`) |
19
19
  | `sudo opencharm setup --domain <d>` | Install charmd as a hardened systemd service on a Linux server (`--dry-run` prints the plan; see `docs/deploy.md`) |
20
20
  | `opencharm pair <code>` | Pair the charm showing that code; you choose its PIN (`--name <name>`) |
21
- | `opencharm status` | What's running (charmd, each connected charm's build, the starter commit of the workspace; spec 015), then the paired charms and whether they are connected, locked or blocked |
21
+ | `opencharm status` | What's running (charmd, each connected charm's build, the starter commit of the workspace; spec 015), then the paired charms and whether they are connected, locked or blocked, with each one's battery when it has one (spec 009) |
22
22
  | `opencharm lock` / `unlock` / `revoke <charm>` | Lock it now; clear a block and unlock; forget it |
23
23
  | `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` |
24
24
  | `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` |
package/dist/main.mjs CHANGED
@@ -527,12 +527,22 @@ const answer = z.object({
527
527
  id: askId,
528
528
  yes: z.boolean()
529
529
  });
530
+ const battery = z.object({
531
+ type: z.literal("charm"),
532
+ op: z.literal("battery"),
533
+ percent: z.number().int().min(0).max(100),
534
+ charging: z.boolean(),
535
+ usb: z.boolean()
536
+ });
530
537
  const serverHello = z.object({
531
538
  type: z.literal("hello"),
532
539
  transport: z.literal("websocket"),
533
540
  session_id: z.string().min(1).max(128),
534
541
  audio_params: audioParams,
535
- features: z.object({ text: z.boolean().optional() }).optional()
542
+ features: z.object({
543
+ text: z.boolean().optional(),
544
+ battery: z.boolean().optional()
545
+ }).optional()
536
546
  });
537
547
  const stt = z.object({
538
548
  session_id: sessionId,
@@ -619,7 +629,8 @@ const CLIENT_SCHEMAS = {
619
629
  abort,
620
630
  "charm:unlock": unlock,
621
631
  "charm:answer": answer,
622
- "charm:text": typed
632
+ "charm:text": typed,
633
+ "charm:battery": battery
623
634
  };
624
635
  const SERVER_SCHEMAS = {
625
636
  hello: serverHello,
@@ -997,7 +1008,7 @@ function sendAdmin(socketPath, request) {
997
1008
  }
998
1009
  //#endregion
999
1010
  //#region package.json
1000
- var version = "0.9.0";
1011
+ var version = "0.11.0";
1001
1012
  function channelToCube(value) {
1002
1013
  if (value < 48) return 0;
1003
1014
  if (value < 115) return 1;
@@ -1147,7 +1158,12 @@ const statusSchema = z.object({
1147
1158
  state: z.string(),
1148
1159
  blocked: z.boolean(),
1149
1160
  failedTries: z.number(),
1150
- build: charmBuild.optional().catch(void 0)
1161
+ build: charmBuild.optional().catch(void 0),
1162
+ battery: z.object({
1163
+ percent: z.number().int().min(0).max(100),
1164
+ charging: z.boolean(),
1165
+ usb: z.boolean()
1166
+ }).optional().catch(void 0)
1151
1167
  }))
1152
1168
  });
1153
1169
  const DONE = {
@@ -1243,7 +1259,13 @@ function printStatus(ctx, charms, charmd, starter, voice) {
1243
1259
  return;
1244
1260
  }
1245
1261
  for (const charm of charms) {
1246
- const details = [charm.blocked ? "blocked (opencharm unlock to clear)" : charm.failedTries > 0 ? `${charm.failedTries} wrong PIN(s)` : "", charm.build ? `${charm.build.kind} ${charm.build.version} (${charm.build.commit})` : ""].filter(Boolean).join(" ");
1262
+ const note = charm.blocked ? "blocked (opencharm unlock to clear)" : charm.failedTries > 0 ? `${charm.failedTries} wrong PIN(s)` : "";
1263
+ const build = charm.build ? `${charm.build.kind} ${charm.build.version} (${charm.build.commit})` : "";
1264
+ const details = [
1265
+ note,
1266
+ charm.battery ? `${charm.battery.percent}%` + (charm.battery.charging ? " charging" : charm.battery.usb ? " on USB" : "") : "",
1267
+ build
1268
+ ].filter(Boolean).join(" ");
1247
1269
  ctx.out.write(`${charm.name.padEnd(16)} ${charm.state.padEnd(10)} ${details}`.trimEnd() + "\n");
1248
1270
  }
1249
1271
  }
@@ -2007,7 +2029,8 @@ function createAdminActions({ store, pairing, sessions, look, agentCanChangeLook
2007
2029
  failedTries: charm.failedTries,
2008
2030
  connected: live !== void 0,
2009
2031
  state: live?.state ?? "offline",
2010
- build: live?.build
2032
+ build: live?.build,
2033
+ battery: live?.battery
2011
2034
  };
2012
2035
  })
2013
2036
  };
@@ -2276,6 +2299,7 @@ var Session = class {
2276
2299
  #state = "awaiting_hello";
2277
2300
  #charmId;
2278
2301
  #build;
2302
+ #battery;
2279
2303
  #queue = Promise.resolve();
2280
2304
  #turn;
2281
2305
  #asks = Promise.resolve();
@@ -2291,6 +2315,9 @@ var Session = class {
2291
2315
  get build() {
2292
2316
  return this.#build;
2293
2317
  }
2318
+ get battery() {
2319
+ return this.#battery;
2320
+ }
2294
2321
  get charmId() {
2295
2322
  return this.#charmId;
2296
2323
  }
@@ -2389,7 +2416,10 @@ var Session = class {
2389
2416
  transport: "websocket",
2390
2417
  session_id: this.sessionId,
2391
2418
  audio_params: AUDIO_DOWN,
2392
- ...this.#build?.kind === "desktop" ? { features: { text: true } } : {}
2419
+ features: {
2420
+ battery: true,
2421
+ ...this.#build?.kind === "desktop" ? { text: true } : {}
2422
+ }
2393
2423
  });
2394
2424
  const { token } = this.#deps;
2395
2425
  if (token === void 0) {
@@ -2424,6 +2454,15 @@ var Session = class {
2424
2454
  this.#sendLocked();
2425
2455
  return;
2426
2456
  }
2457
+ if (message.type === "charm" && message.op === "battery") {
2458
+ const { percent, charging, usb } = message;
2459
+ this.#battery = {
2460
+ percent,
2461
+ charging,
2462
+ usb
2463
+ };
2464
+ return;
2465
+ }
2427
2466
  if (message.type === "charm" && message.op === "answer") {
2428
2467
  if (this.#pendingAsk?.id === message.id) this.#pendingAsk.settle(message.yes);
2429
2468
  return;
@@ -2569,6 +2608,7 @@ var Session = class {
2569
2608
  this.#pendingAsk?.drop();
2570
2609
  this.#endTurn();
2571
2610
  this.#state = "locked";
2611
+ this.#battery = void 0;
2572
2612
  this.#send({
2573
2613
  type: "charm",
2574
2614
  op: "locked",
@@ -5056,7 +5096,7 @@ function formatIdentity(unit, version, commit) {
5056
5096
  let cached;
5057
5097
  function identity() {
5058
5098
  if (cached) return cached;
5059
- const commit = "2cd5780";
5099
+ const commit = "23172c6";
5060
5100
  cached = {
5061
5101
  version: `cli@${version}`,
5062
5102
  commit,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencharm",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
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": {
Binary file