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.
- package/README.md +12 -11
- package/carmoji.js +19 -14
- 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`
|
|
159
|
-
which fires **only when a permission dialog is
|
|
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
|
-
|
|
169
|
-
question with 2–4 labeled options becomes a tappable option
|
|
170
|
-
phone (`npx carmoji test question` demos it), and your pick is
|
|
171
|
-
as the answer. Richer questions — several at once, multi-select,
|
|
172
|
-
text — still show as a pleading toast and are answered in the terminal.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
|
617
|
-
* `hookSpecificOutput.decision
|
|
618
|
-
|
|
619
|
-
|
|
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
|
|
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
|
|
1607
|
-
single-select question with 2–4 labeled options; the tapped label
|
|
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:
|