carmoji 0.3.6 → 0.3.7

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.
Files changed (3) hide show
  1. package/README.md +12 -11
  2. package/carmoji.js +19 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -153,10 +153,11 @@ cache-write tokens; cache reads excluded). It's your real consumption, not
153
153
  an official percentage of plan quota — there's no public API for that.
154
154
  Recomputed at turn boundaries with a 5-minute cache so hooks stay fast.
155
155
 
156
- ## Answer permissions from the phone (Claude Code)
156
+ ## Answer permissions from the phone (Claude Code and Codex)
157
157
 
158
- `carmoji install claude` hooks Claude Code's `PermissionRequest` event,
159
- which fires **only when a permission dialog is about to appear** —
158
+ `carmoji install claude` and `carmoji install codex` hook each agent's
159
+ `PermissionRequest` event, which fires **only when a permission dialog is
160
+ about to appear** —
160
161
  allowlisted tools and auto-accepted edits never trigger it, so nothing
161
162
  ever slows down a call that would have run anyway. When it fires, the
162
163
  phone chimes (a doorbell sound used by nothing else), shows big pleading
@@ -165,14 +166,14 @@ settles the prompt on the computer via the hook's official decision
165
166
  output. No answer within ~55 s falls through to the normal terminal
166
167
  prompt (`npx carmoji test approval` exercises the whole loop).
167
168
 
168
- Simple **AskUserQuestion** pickers ride the same hook: one single-select
169
- question with 2–4 labeled options becomes a tappable option card on the
170
- phone (`npx carmoji test question` demos it), and your pick is fed back
171
- as the answer. Richer questions — several at once, multi-select, free
172
- text — still show as a pleading toast and are answered in the terminal.
173
-
174
- Honest limits: the Codex integration currently treats `PermissionRequest`
175
- as display-only (answering from the phone is planned next).
169
+ For Claude Code, simple **AskUserQuestion** pickers ride the same hook: one
170
+ single-select question with 2–4 labeled options becomes a tappable option
171
+ card on the phone (`npx carmoji test question` demos it), and your pick is
172
+ fed back as the answer. Richer questions — several at once, multi-select,
173
+ free text — still show as a pleading toast and are answered in the terminal.
174
+ Codex questions use its App Server `requestUserInput` protocol rather than
175
+ lifecycle hooks, so they remain terminal-only; Codex Allow/Deny permission
176
+ approvals do work from the phone.
176
177
 
177
178
  Older Claude Code versions without the `PermissionRequest` hook can use
178
179
  `npx carmoji config gate` instead: a legacy `PreToolUse` gate with a tool
package/carmoji.js CHANGED
@@ -613,10 +613,14 @@ const APPROVAL_WAIT_MS = 55_000;
613
613
  const HOLD_WAIT_MS = 120_000;
614
614
  const PERMISSION_HOOK_TIMEOUT_S = 150;
615
615
 
616
- /** Sources whose PermissionRequest hook accepts a decision back via
617
- * `hookSpecificOutput.decision` (Claude Code's schema). Codex is the next
618
- * candidate once its hooks are verified to honor the reply. */
619
- const ANSWERABLE_PERMISSION_SOURCES = new Set(['claude']);
616
+ /** Sources whose PermissionRequest hook accepts Allow/Deny through
617
+ * `hookSpecificOutput.decision`. */
618
+ const ANSWERABLE_APPROVAL_SOURCES = new Set(['claude', 'codex']);
619
+
620
+ /** Sources whose question protocol accepts `decision.updatedInput`.
621
+ * Codex questions use App Server requestUserInput instead of hooks, so a
622
+ * synthetic AskUserQuestion event must remain display-only. */
623
+ const ANSWERABLE_QUESTION_SOURCES = new Set(['claude']);
620
624
 
621
625
  // ---------------------------------------------------------------------------
622
626
  // Multi-tool hook installers (see EVENT_ALIASES for how their events map).
@@ -1245,8 +1249,7 @@ async function main() {
1245
1249
  // to the terminal dialog.
1246
1250
  const canonicalEvent = EVENT_ALIASES[payload.hook_event_name]
1247
1251
  ?? payload.hook_event_name;
1248
- if (canonicalEvent === 'PermissionRequest'
1249
- && ANSWERABLE_PERMISSION_SOURCES.has(source)) {
1252
+ if (canonicalEvent === 'PermissionRequest') {
1250
1253
  const common = {
1251
1254
  source,
1252
1255
  session: payload.session_id,
@@ -1260,7 +1263,8 @@ async function main() {
1260
1263
  // looks answers up by it — keying by header yields empty answers)
1261
1264
  // with the original `questions` kept in place (Claude Code crashes
1262
1265
  // on its absence). Richer shapes fall through to display-only.
1263
- if (payload.tool_name === 'AskUserQuestion') {
1266
+ if (payload.tool_name === 'AskUserQuestion'
1267
+ && ANSWERABLE_QUESTION_SOURCES.has(source)) {
1264
1268
  const question = answerableQuestion(payload.tool_input);
1265
1269
  if (question) {
1266
1270
  const reply = await requestApprovalAll(devices, {
@@ -1292,7 +1296,8 @@ async function main() {
1292
1296
  }
1293
1297
  // No card, no tap, or an unknown label: fall through to the
1294
1298
  // pleading face + toast; the picker stays in the terminal.
1295
- } else {
1299
+ } else if (payload.tool_name !== 'AskUserQuestion'
1300
+ && ANSWERABLE_APPROVAL_SOURCES.has(source)) {
1296
1301
  const decision = (await requestApprovalAll(devices, {
1297
1302
  ...common,
1298
1303
  event: 'approval_request',
@@ -1389,7 +1394,7 @@ async function main() {
1389
1394
  }, APPROVAL_WAIT_MS))?.decision;
1390
1395
  console.log(decision
1391
1396
  ? `Decision: ${decision}`
1392
- : 'No answer — in a real session Claude Code would fall through to the terminal prompt.');
1397
+ : 'No answer — in a real session the coding agent would fall through to the terminal prompt.');
1393
1398
  process.exit(decision ? 0 : 1);
1394
1399
  }
1395
1400
  if (arg === 'question') {
@@ -1599,16 +1604,16 @@ tool's dialect (beforeShellExecution, TaskComplete, permission_request, …);
1599
1604
  see EVENT_ALIASES in carmoji.js for the full mapping. Unknown events are
1600
1605
  silently ignored.
1601
1606
 
1602
- Blocking: a Claude Code PermissionRequest holds the hook open up to ${APPROVAL_WAIT_MS / 1000}s
1607
+ Blocking: a Claude Code or Codex PermissionRequest holds the hook open up to ${APPROVAL_WAIT_MS / 1000}s
1603
1608
  while the phone shows a card, then prints the decision for the agent:
1604
1609
  {"hookSpecificOutput":{"hookEventName":"PermissionRequest",
1605
1610
  "decision":{"behavior":"allow"}}}
1606
- AskUserQuestion gets an option-picker card instead when it's one
1607
- single-select question with 2–4 labeled options; the tapped label — or
1608
- free text typed behind the card's "Other…" button, which sends
1611
+ For Claude Code, AskUserQuestion gets an option-picker card instead when
1612
+ it's one single-select question with 2–4 labeled options; the tapped label
1613
+ — or free text typed behind the card's "Other…" button, which sends
1609
1614
  {hold:true} to stretch the wait to ${HOLD_WAIT_MS / 1000}s — comes back via
1610
1615
  decision.updatedInput.answers. Multi-question and multiSelect stay
1611
- display-only.
1616
+ display-only; Codex questions use App Server requestUserInput instead.
1612
1617
  No tap → no output → the normal terminal prompt appears.
1613
1618
 
1614
1619
  Try it:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "carmoji",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "Bring your CarMoji pet to life from Claude Code / Codex — a LAN bridge that streams coding events to the CarMoji iOS app",
5
5
  "type": "module",
6
6
  "bin": {