opencharm 0.6.2 → 0.7.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
@@ -6,25 +6,25 @@ The command line for [OpenCharm](https://opencharm.dev): the open-source charm f
6
6
  npx opencharm
7
7
  ```
8
8
 
9
- | Command | What it does |
10
- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
- | `opencharm` | Says hello with a blinking face |
12
- | `opencharm faces` | Lists all 22 moods |
13
- | `opencharm face [mood]` | Shows one mood (random if omitted), e.g. `opencharm face learned` |
14
- | `opencharm states` | What your agent is doing, and the face for each state |
15
- | `opencharm hardware` | What to buy and print |
16
- | `opencharm init [dir]` | Clone the starter workspace ([opencharm-starter](https://github.com/opencharm-labs/opencharm-starter)) and fit it to this machine (`--agent claude` (default), `codex`, `gemini`, `goose`, `hermes`, `openclaw`; `--from <git url>` for a fork; `--name <name>` and `--colour <colour>` write the charm's identity, the `charm` block) |
17
- | `opencharm serve` | Start charmd, the charm daemon, next to your agent (`--config <file>`) |
18
- | `opencharm sim` | The charm on screen, in your browser, talking to charmd (`--url`, `--port`, `--no-open`) |
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
- | `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 |
22
- | `opencharm lock` / `unlock` / `revoke <charm>` | Lock it now; clear a block and unlock; forget it |
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
- | `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 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) |
26
- | `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) |
27
- | `opencharm mcp` | The charm's tools for an agent (MCP over stdio): `say`, `show_face`, `ask`, `notify`, `set_look`. 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) |
9
+ | Command | What it does |
10
+ | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
11
+ | `opencharm` | Says hello with a blinking face |
12
+ | `opencharm faces` | Lists all 22 moods |
13
+ | `opencharm face [mood]` | Shows one mood (random if omitted), e.g. `opencharm face learned` |
14
+ | `opencharm states` | What your agent is doing, and the face for each state |
15
+ | `opencharm hardware` | What to buy and print |
16
+ | `opencharm init [dir]` | Clone the starter workspace ([opencharm-starter](https://github.com/opencharm-labs/opencharm-starter)) and fit it to this machine (`--agent claude` (default), `codex`, `gemini`, `goose`, `hermes`, `openclaw`; `--from <git url>` for a fork; `--name <name>` and `--colour <colour>` (one of the six, or `#RRGGBB`) write the charm's identity, the `charm` block) |
17
+ | `opencharm serve` | Start charmd, the charm daemon, next to your agent (`--config <file>`) |
18
+ | `opencharm sim` | The charm on screen, in your browser, talking to charmd (`--url`, `--port`, `--no-open`) |
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
+ | `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 |
22
+ | `opencharm lock` / `unlock` / `revoke <charm>` | Lock it now; clear a block and unlock; forget it |
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
+ | `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 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) |
26
+ | `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) |
27
+ | `opencharm mcp` | The charm's tools for an agent (MCP over stdio): `say`, `show_face`, `ask`, `notify`, `set_look`. 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) |
28
28
 
29
29
  Options: `--colour white|cobalt|lime|lilac|sun|coal`, `--no-anim`, `--no-color` (also respects `NO_COLOR`).
30
30
 
package/dist/main.mjs CHANGED
@@ -629,10 +629,33 @@ const MAX_NAME_CHARS = 12;
629
629
  const MAX_NAME_BYTES = 24;
630
630
  const MAX_GREETING_BYTES = 40;
631
631
  const COLOUR_IDS = facesData.colours.map((colour) => colour.id);
632
+ const OWN_COLOUR = /^#[0-9A-Fa-f]{6}$/;
632
633
  const lookName = z.string().min(1, "the name can't be empty").refine((name) => [...name].length <= MAX_NAME_CHARS && utf8Length(name) <= MAX_NAME_BYTES, { message: `at most ${MAX_NAME_CHARS} characters` });
634
+ function ownColourProblem(hex) {
635
+ const [r, g, b] = [
636
+ 1,
637
+ 3,
638
+ 5
639
+ ].map((at) => Number.parseInt(hex.slice(at, at + 2), 16) / 255);
640
+ const linear = (v) => v <= .04045 ? v / 12.92 : ((v + .055) / 1.055) ** 2.4;
641
+ if (.2126 * linear(r) + .7152 * linear(g) + .0722 * linear(b) < .05) return "too dark to see on black";
642
+ const max = Math.max(r, g, b);
643
+ const spread = max - Math.min(r, g, b);
644
+ if (max !== r || max < .5 || spread / max < .5) return void 0;
645
+ const hue = 60 * (g - b) / spread;
646
+ return hue >= 6 && hue <= 40 ? "too close to the orange that means 'it needs you'" : void 0;
647
+ }
648
+ const colour = z.string().superRefine((value, context) => {
649
+ if (COLOUR_IDS.includes(value)) return;
650
+ const problem = OWN_COLOUR.test(value) ? ownColourProblem(value) : `the colour is one of ${COLOUR_IDS.join(", ")}, or your own as #RRGGBB`;
651
+ if (problem) context.addIssue({
652
+ code: "custom",
653
+ message: problem
654
+ });
655
+ }).transform((value) => OWN_COLOUR.test(value) ? value.toUpperCase() : value);
633
656
  const lookFields = {
634
657
  name: lookName.optional(),
635
- colour: z.enum(COLOUR_IDS, { error: `the colour is one of ${COLOUR_IDS.join(", ")}` }).optional(),
658
+ colour: colour.optional(),
636
659
  greeting: z.string().refine((text) => utf8Length(text) <= MAX_GREETING_BYTES, { message: `at most ${MAX_GREETING_BYTES} bytes` }).optional(),
637
660
  sleepAfterMinutes: z.number().int().min(0).max(1440).optional(),
638
661
  motion: z.enum(["full", "calm"]).optional()
@@ -642,6 +665,7 @@ function issues(error) {
642
665
  return error.issues.map((issue) => `${issue.path.join(".") || "look"}: ${issue.message}`).join("; ");
643
666
  }
644
667
  function glyphOf(colourId) {
668
+ if (OWN_COLOUR.test(colourId)) return colourId;
645
669
  const colour = facesData.colours.find((c) => c.id === colourId);
646
670
  if (!colour) throw new Error(`Unknown colour "${colourId}"`);
647
671
  return colour.g;
@@ -969,7 +993,7 @@ function sendAdmin(socketPath, request) {
969
993
  }
970
994
  //#endregion
971
995
  //#region package.json
972
- var version = "0.6.2";
996
+ var version = "0.7.0";
973
997
  function channelToCube(value) {
974
998
  if (value < 48) return 0;
975
999
  if (value < 115) return 1;
@@ -1463,7 +1487,7 @@ function runHelp(ctx) {
1463
1487
  opencharm hardware what to buy and print
1464
1488
 
1465
1489
  charmd, the charm daemon (runs next to your agent)
1466
- opencharm init [dir] clone the starter workspace for your agent [--agent claude|codex|gemini|goose|hermes|openclaw] [--from <git url>] [--name <name>] [--colour <colour>]
1490
+ opencharm init [dir] clone the starter workspace for your agent [--agent claude|codex|gemini|goose|hermes|openclaw] [--from <git url>] [--name <name>] [--colour <colour|#RRGGBB>]
1467
1491
  opencharm setup --domain <d> install charmd as a service (Linux, sudo) [--dry-run]
1468
1492
  opencharm serve start charmd [--config <file>]
1469
1493
  opencharm sim the charm on screen, in your browser [--url <ws>] [--port <n>]
@@ -1474,7 +1498,7 @@ function runHelp(ctx) {
1474
1498
  opencharm unlock <charm> clear a block and unlock
1475
1499
  opencharm revoke <charm> forget a charm (it must pair again)
1476
1500
  opencharm replies on|off the desktop charm speaks its replies, or shows them as text
1477
- opencharm look show or change the charm's look until charmd restarts [--colour <c>] [--greeting <text>] [--motion full|calm]
1501
+ opencharm look show or change the charm's look until charmd restarts [--colour <c|#RRGGBB>] [--greeting <text>] [--motion full|calm]
1478
1502
  opencharm dev face <charm> <state> [text] show a face (testing)
1479
1503
  opencharm dev say <charm> <text> speak through it (testing)
1480
1504
  opencharm dev ask <charm> <question> ask it; hold = yes, press = no (testing)
@@ -1582,7 +1606,7 @@ const VERSIONS = [
1582
1606
  "2024-11-05"
1583
1607
  ];
1584
1608
  const FACES = facesData.states.map((state) => state.id);
1585
- const COLOURS = facesData.colours.map((colour) => colour.id);
1609
+ const COLOUR = `^(${facesData.colours.map((colour) => colour.id).join("|")}|#[0-9A-Fa-f]{6})$`;
1586
1610
  const line = z.string().min(1).max(200);
1587
1611
  const label = z.string().min(1).max(12);
1588
1612
  const TOOLS = [
@@ -1701,8 +1725,8 @@ const TOOLS = [
1701
1725
  properties: {
1702
1726
  colour: {
1703
1727
  type: "string",
1704
- enum: COLOURS,
1705
- description: "cobalt is blue, lime green, lilac purple, sun yellow; white and coal light the glyphs white."
1728
+ pattern: COLOUR,
1729
+ description: "cobalt is blue, lime green, lilac purple, sun yellow; white and coal light the glyphs white. Any other colour as #RRGGBB (not orange, not too dark)."
1706
1730
  },
1707
1731
  greeting: {
1708
1732
  type: "string",
@@ -1716,7 +1740,7 @@ const TOOLS = [
1716
1740
  }
1717
1741
  },
1718
1742
  args: z.object({
1719
- colour: z.enum(COLOURS).optional(),
1743
+ colour: z.string().regex(new RegExp(COLOUR)).optional(),
1720
1744
  greeting: z.string().max(40).optional(),
1721
1745
  motion: z.enum(["full", "calm"]).optional()
1722
1746
  }).strict(),
@@ -4941,7 +4965,7 @@ function formatIdentity(unit, version, commit) {
4941
4965
  let cached;
4942
4966
  function identity() {
4943
4967
  if (cached) return cached;
4944
- const commit = "d1f99c9";
4968
+ const commit = "d601217";
4945
4969
  cached = {
4946
4970
  version: `cli@${version}`,
4947
4971
  commit,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencharm",
3
- "version": "0.6.2",
3
+ "version": "0.7.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