@zeph-to/cli 2.16.0 → 2.17.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 +23 -1
- package/dist/templates.d.ts +3 -1
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +129 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -664,6 +664,28 @@ others cannot: a turn with no tool calls, a turn with exactly one (a
|
|
|
664
664
|
lone edit included — the gate wants two), and a turn whose calls were
|
|
665
665
|
all reads. `/zeph-loud` still pushes on all of them.
|
|
666
666
|
|
|
667
|
+
The Pi extension also pushes when pi is **waiting on you**: any blocking
|
|
668
|
+
extension dialog — a bash guard's Run/Abort, an ask-user tool, a
|
|
669
|
+
`ctx.ui.confirm` — that is still open 10 seconds after it appeared sends a
|
|
670
|
+
`high` push ("pi asks: <project>", body = the dialog title or the guarded
|
|
671
|
+
bash command). `high` gets through `quiet`; only `/zeph-mute` stops it.
|
|
672
|
+
Answering inside the 10 seconds sends nothing. It is the pi twin of the
|
|
673
|
+
Claude Code plugin's AskUserQuestion push, driven by pi's
|
|
674
|
+
`ui_prompt_start` / `ui_prompt_end` events. Only dialogs that open while a
|
|
675
|
+
turn is running count: a settings picker you open yourself between turns
|
|
676
|
+
(`/caveman config`) never pushes.
|
|
677
|
+
|
|
678
|
+
Two more end-of-turn cases differ from Claude Code:
|
|
679
|
+
|
|
680
|
+
- **Push Signal markers** — pi's rules teach the same `<!-- zeph: skip|push|high -->`
|
|
681
|
+
markers, and the extension reads them from the final assistant message
|
|
682
|
+
(then strips them, since pi's terminal would otherwise print the comment).
|
|
683
|
+
`high` is how pi gets a "the turn ended with a question for you" push
|
|
684
|
+
through `quiet`.
|
|
685
|
+
- **Errors and aborts** — a turn that ended on a provider error sends a
|
|
686
|
+
`high` "pi stopped: <project>" push with the error text instead of
|
|
687
|
+
"Task done"; a turn you aborted with Esc sends nothing.
|
|
688
|
+
|
|
667
689
|
#### Quiet while away
|
|
668
690
|
|
|
669
691
|
`quiet` silences routine pushes because you are watching the pane. When
|
|
@@ -789,7 +811,7 @@ try {
|
|
|
789
811
|
| Copilot CLI | Session end hook + rules |
|
|
790
812
|
| Cline | Rules file (`~/.cline/rules/zeph.md`) |
|
|
791
813
|
| Aider | Conventions file + `read:` directive in `~/.aider.conf.yml` |
|
|
792
|
-
| Pi | Extension (settle hook + remote detection) + rules — no MCP (pi has none; rules map zeph tools to the CLI) |
|
|
814
|
+
| Pi | Extension (settle hook + waiting-on-you push + remote detection) + rules — no MCP (pi has none; rules map zeph tools to the CLI) |
|
|
793
815
|
| OpenCode | MCP server + idle-notify plugin + rules |
|
|
794
816
|
|
|
795
817
|
For remote-control via `zeph listener` the per-agent setup is the same
|
package/dist/templates.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Grace before the pi extension turns an unanswered prompt into a push. Exported for the tests. */
|
|
2
|
+
export declare const PI_PROMPT_GRACE_MS = 10000;
|
|
1
3
|
/** Cursor — written to ~/.cursor/rules/zeph.mdc (needs .mdc frontmatter). No prompt hook. */
|
|
2
4
|
export declare const CURSOR_RULE: string;
|
|
3
5
|
/** Windsurf — appended into ~/.codeium/windsurf/memories/global_rules.md. No prompt hook. */
|
|
@@ -12,7 +14,7 @@ export declare const COPILOT_RULE: string;
|
|
|
12
14
|
export declare const CLINE_RULE: string;
|
|
13
15
|
/** Aider — standalone conventions file via .aider.conf.yml `read:` (no Stop hook, no prompt hook). */
|
|
14
16
|
export declare const AIDER_RULE: string;
|
|
15
|
-
/** Pi — managed block in ~/.pi/agent/AGENTS.md. Extension = Stop-equivalent + prompt hook (PI_EXTENSION). */
|
|
17
|
+
/** Pi — managed block in ~/.pi/agent/AGENTS.md. Extension = Stop-equivalent + prompt hook + waiting-on-you push (PI_EXTENSION). */
|
|
16
18
|
export declare const PI_RULE: string;
|
|
17
19
|
/** OpenCode — managed block in ~/.config/opencode/AGENTS.md. Stop hook via plugin, no prompt hook (v1). */
|
|
18
20
|
export declare const OPENCODE_RULE: string;
|
package/dist/templates.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AA+FA,oGAAoG;AACpG,eAAO,MAAM,kBAAkB,QAAS,CAAC;AAoHzC,6FAA6F;AAC7F,eAAO,MAAM,WAAW,QAKtB,CAAC;AAEH,6FAA6F;AAC7F,eAAO,MAAM,aAAa,QAIxB,CAAC;AAEH,0FAA0F;AAC1F,eAAO,MAAM,WAAW,QAAyE,CAAC;AAElG,uFAAuF;AACvF,eAAO,MAAM,UAAU,QAAyE,CAAC;AAEjG,oGAAoG;AACpG,eAAO,MAAM,YAAY,QAIvB,CAAC;AAEH,gFAAgF;AAChF,eAAO,MAAM,UAAU,QAIrB,CAAC;AAEH,sGAAsG;AACtG,eAAO,MAAM,UAAU,QAIrB,CAAC;AAEH,mIAAmI;AACnI,eAAO,MAAM,OAAO,QAKlB,CAAC;AAEH,2GAA2G;AAC3G,eAAO,MAAM,aAAa,QAIxB,CAAC;AAIH,eAAO,MAAM,YAAY,QAKd,CAAC;AAEZ,eAAO,MAAM,cAAc,QAOhB,CAAC;AAEZ,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;CAuBxB,CAAC;AAYF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;CASvB,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,OAAO,KAAG,OAUhD,CAAC;AAEF,eAAO,MAAM,aAAa,QASf,CAAC;AAQZ,2FAA2F;AAC3F,eAAO,MAAM,YAAY,QAgJxB,CAAC;AAEF,kGAAkG;AAClG,eAAO,MAAM,eAAe,QAwG3B,CAAC;AASF,eAAO,MAAM,eAAe,oFAA+E,CAAC;AAC5G,eAAO,MAAM,aAAa,sBAAsB,CAAC;AAQjD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,EAAE,MAAM,MAAM,KAAG,MAWnE,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,KAAG,MAOrD,CAAC"}
|
package/dist/templates.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
// Keeping this in one place means a rule change lands everywhere at once
|
|
27
27
|
// and the agents can't drift apart.
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.removeManagedBlock = exports.upsertManagedBlock = exports.ZEPH_MARK_END = exports.ZEPH_MARK_START = exports.OPENCODE_PLUGIN = exports.PI_EXTENSION = exports.COPILOT_HOOKS = exports.isZephHookGroup = exports.CODEX_HOOKS = exports.GEMINI_HOOKS = exports.WINDSURF_HOOKS = exports.CURSOR_HOOKS = exports.OPENCODE_RULE = exports.PI_RULE = exports.AIDER_RULE = exports.CLINE_RULE = exports.COPILOT_RULE = exports.CODEX_RULE = exports.GEMINI_RULE = exports.WINDSURF_RULE = exports.CURSOR_RULE = void 0;
|
|
29
|
+
exports.removeManagedBlock = exports.upsertManagedBlock = exports.ZEPH_MARK_END = exports.ZEPH_MARK_START = exports.OPENCODE_PLUGIN = exports.PI_EXTENSION = exports.COPILOT_HOOKS = exports.isZephHookGroup = exports.CODEX_HOOKS = exports.GEMINI_HOOKS = exports.WINDSURF_HOOKS = exports.CURSOR_HOOKS = exports.OPENCODE_RULE = exports.PI_RULE = exports.AIDER_RULE = exports.CLINE_RULE = exports.COPILOT_RULE = exports.CODEX_RULE = exports.GEMINI_RULE = exports.WINDSURF_RULE = exports.CURSOR_RULE = exports.PI_PROMPT_GRACE_MS = void 0;
|
|
30
30
|
const gate_js_1 = require("./gate.js");
|
|
31
31
|
const zeph_core_generated_js_1 = require("./zeph-core.generated.js");
|
|
32
32
|
// Graceful resolution: prefer the installed `zeph` CLI, but fall back to
|
|
@@ -73,6 +73,19 @@ const TURN_FACT_FLAGS = ` --${gate_js_1.TOOL_COUNT_FLAG} \${tools} --${gate_js_1
|
|
|
73
73
|
// stderr is discarded; `|| true` keeps a broken install from ever blocking
|
|
74
74
|
// a prompt.
|
|
75
75
|
const remoteHookCmd = (agent) => `$(command -v zeph || echo "npx -y @zeph-to/cli") remote-hook ${agent} 2>/dev/null || true`;
|
|
76
|
+
// One-off `high` push from the pi extension — a prompt left waiting (the twin
|
|
77
|
+
// of the plugin's zeph-ask.sh) or a turn that died on an error. `high` gets
|
|
78
|
+
// through the quiet dial, and plain `notify` (no --auto) means only a mute
|
|
79
|
+
// stops it. Title and body arrive as env vars, never spliced into the string:
|
|
80
|
+
// the body can carry the model's own bash command or a provider error.
|
|
81
|
+
const highNotifyCmd = '$(command -v zeph || echo "npx -y @zeph-to/cli") notify'
|
|
82
|
+
+ ' --title "$ZEPH_PUSH_TITLE" --body "$ZEPH_PUSH_BODY" --priority high 2>/dev/null || true';
|
|
83
|
+
// The pi turn's Push Signal marker, as a JS template placeholder (single quotes:
|
|
84
|
+
// `${marker}` must reach the artifact unexpanded). The artifact only ever
|
|
85
|
+
// assigns it a word the marker regex captured, or "none".
|
|
86
|
+
const PUSH_SIGNAL_FLAG = ' --marker ${marker}';
|
|
87
|
+
/** Grace before the pi extension turns an unanswered prompt into a push. Exported for the tests. */
|
|
88
|
+
exports.PI_PROMPT_GRACE_MS = 10_000;
|
|
76
89
|
// ── Shared behavioral core ───────────────────────────────────────
|
|
77
90
|
//
|
|
78
91
|
// GENERATED from plugin/docs/CORE_RULES.md — see src/zeph-core.generated.ts
|
|
@@ -137,10 +150,31 @@ tool instead — same semantics:
|
|
|
137
150
|
unreachable) is a Done-like outcome — treat it as NORMAL.
|
|
138
151
|
- zeph_notify → \`zeph notify --title "…" --body "…" [--priority high]\`
|
|
139
152
|
- AskUserQuestion → pi's own terminal prompt.`;
|
|
153
|
+
// Push Signal preamble — pi only. The shared core has no Push Signal section:
|
|
154
|
+
// Claude Code gets it from the plugin's SessionStart hook, per push dial. The
|
|
155
|
+
// pi extension reads the same markers (PI_EXTENSION, message_end), so pi's
|
|
156
|
+
// rules carry a dial-independent version here.
|
|
157
|
+
const PI_PUSH_SIGNAL = `## Push Signal — steer the end-of-turn push
|
|
158
|
+
|
|
159
|
+
When a turn ends, the zeph extension decides whether to push by tool volume
|
|
160
|
+
and the user's push dial. Put ONE marker anywhere in your final response to
|
|
161
|
+
override that for this turn. The extension removes it before the message is
|
|
162
|
+
shown or kept:
|
|
163
|
+
|
|
164
|
+
- \`<!-- zeph: high -->\` — push at high priority. The only marker that gets
|
|
165
|
+
through the quiet dial, so use it whenever the user must act: you ended the
|
|
166
|
+
turn with a question for them, or you are blocked on a decision only they
|
|
167
|
+
can make. Never on routine completions.
|
|
168
|
+
- \`<!-- zeph: push -->\` — push a turn the heuristic would skip.
|
|
169
|
+
- \`<!-- zeph: skip -->\` — no push for a turn not worth a ping.
|
|
170
|
+
|
|
171
|
+
No marker → the heuristic: fewer than 2 tool calls, or only read-only ones,
|
|
172
|
+
stays silent. Markers are lowercase and exact.`;
|
|
140
173
|
/** Assemble a full rule document from optional frontmatter + preambles + core. */
|
|
141
174
|
const buildRule = (opts) => {
|
|
142
175
|
const fm = opts.frontmatter ? `${opts.frontmatter}\n\n` : '';
|
|
143
176
|
const tools = opts.toolAccess ? `${opts.toolAccess}\n\n` : '';
|
|
177
|
+
const signal = opts.pushSignal ? `${opts.pushSignal}\n\n` : '';
|
|
144
178
|
const entry = opts.remoteEntry ? `${opts.remoteEntry}\n\n` : '';
|
|
145
179
|
return `${fm}# Zeph — Remote-Control Rules
|
|
146
180
|
|
|
@@ -150,7 +184,7 @@ the user.
|
|
|
150
184
|
|
|
151
185
|
${opts.notify}
|
|
152
186
|
|
|
153
|
-
${tools}${entry}${opts.core}
|
|
187
|
+
${tools}${signal}${entry}${opts.core}
|
|
154
188
|
`;
|
|
155
189
|
};
|
|
156
190
|
// ── Per-agent rule documents ─────────────────────────────────────
|
|
@@ -193,10 +227,11 @@ exports.AIDER_RULE = buildRule({
|
|
|
193
227
|
remoteEntry: REMOTE_ENTRY_NO_HOOK,
|
|
194
228
|
core: zeph_core_generated_js_1.ZEPH_CORE_RULE_ONLY,
|
|
195
229
|
});
|
|
196
|
-
/** Pi — managed block in ~/.pi/agent/AGENTS.md. Extension = Stop-equivalent + prompt hook (PI_EXTENSION). */
|
|
230
|
+
/** Pi — managed block in ~/.pi/agent/AGENTS.md. Extension = Stop-equivalent + prompt hook + waiting-on-you push (PI_EXTENSION). */
|
|
197
231
|
exports.PI_RULE = buildRule({
|
|
198
232
|
notify: HOOK_DRIVEN_NOTIFY,
|
|
199
233
|
toolAccess: PI_TOOL_ACCESS,
|
|
234
|
+
pushSignal: PI_PUSH_SIGNAL,
|
|
200
235
|
core: zeph_core_generated_js_1.ZEPH_CORE_HOOK_DRIVEN,
|
|
201
236
|
});
|
|
202
237
|
/** OpenCode — managed block in ~/.config/opencode/AGENTS.md. Stop hook via plugin, no prompt hook (v1). */
|
|
@@ -320,20 +355,108 @@ const sh = (cmd: string, cwd: string, stdin?: string): Promise<string> =>
|
|
|
320
355
|
// an unknown tool errs toward pushing rather than toward silence.
|
|
321
356
|
const READ_ONLY = new Set(["read", "grep", "find", "ls"]);
|
|
322
357
|
|
|
358
|
+
// How long a blocking prompt may stay open before it becomes a push. An answer
|
|
359
|
+
// inside the window means the user is at the terminal, and a guard extension
|
|
360
|
+
// can raise several prompts a turn — pushing each one at once would be noise.
|
|
361
|
+
const PROMPT_GRACE_MS = ${exports.PI_PROMPT_GRACE_MS};
|
|
362
|
+
|
|
363
|
+
// Push Signal markers — the pattern the Claude Code Stop hook reads
|
|
364
|
+
// (plugin/hooks/zeph-stop.sh MARKER_RE). pi's markdown renders an HTML comment
|
|
365
|
+
// as plain text, so message_end also strips it from what the user sees.
|
|
366
|
+
const MARKER_RE = /<!--[ \\t]*zeph:[ \\t]*(skip|push|high)[ \\t]*-->/g;
|
|
367
|
+
|
|
368
|
+
const projectOf = (cwd: string): string => cwd.split("/").pop() || cwd;
|
|
369
|
+
|
|
370
|
+
// Fire-and-forget: never block pi on the notify network call.
|
|
371
|
+
const pushHigh = (cwd: string, title: string, body: string): void => {
|
|
372
|
+
const env = {
|
|
373
|
+
...process.env,
|
|
374
|
+
ZEPH_PUSH_TITLE: title,
|
|
375
|
+
ZEPH_PUSH_BODY: body.length > 200 ? body.slice(0, 199) + "…" : body,
|
|
376
|
+
};
|
|
377
|
+
const child = spawn("sh", ["-c", ${JSON.stringify(highNotifyCmd)}], { cwd, env, stdio: "ignore", detached: true });
|
|
378
|
+
child.on("error", () => {});
|
|
379
|
+
child.unref();
|
|
380
|
+
};
|
|
381
|
+
|
|
323
382
|
export default function (pi: ExtensionAPI) {
|
|
324
383
|
// Turn facts for the push gate. One agent per process, so plain counters
|
|
325
384
|
// suffice — no session keying.
|
|
326
385
|
let tools = 0;
|
|
327
386
|
let nonReadonly = 0;
|
|
387
|
+
// The tool call in flight. tool_execution_start fires before pi runs the
|
|
388
|
+
// tool_call handlers, so a guard's prompt always finds its own call here.
|
|
389
|
+
let running: { toolName: string; args: any } | undefined;
|
|
390
|
+
let promptTimer: ReturnType<typeof setTimeout> | undefined;
|
|
391
|
+
// A prompt only counts inside a turn. Outside one it is a dialog the user
|
|
392
|
+
// opened at the terminal themselves (/caveman config, a settings picker).
|
|
393
|
+
let agentRunning = false;
|
|
394
|
+
// This turn's Push Signal, and why its last assistant message stopped.
|
|
395
|
+
let marker = "none";
|
|
396
|
+
let stop: { reason: string; error?: string } | undefined;
|
|
328
397
|
|
|
398
|
+
pi.on("agent_start", () => {
|
|
399
|
+
agentRunning = true;
|
|
400
|
+
});
|
|
401
|
+
pi.on("message_end", (event) => {
|
|
402
|
+
const message = event.message;
|
|
403
|
+
if (message.role !== "assistant") return;
|
|
404
|
+
stop = { reason: message.stopReason, error: message.errorMessage };
|
|
405
|
+
let found = false;
|
|
406
|
+
const content = message.content.map((part) => {
|
|
407
|
+
if (part.type !== "text") return part;
|
|
408
|
+
const text = part.text.replace(MARKER_RE, (_match: string, word: string) => {
|
|
409
|
+
marker = word;
|
|
410
|
+
found = true;
|
|
411
|
+
return "";
|
|
412
|
+
});
|
|
413
|
+
return text === part.text ? part : { ...part, text: text.trimEnd() };
|
|
414
|
+
});
|
|
415
|
+
if (found) return { message: { ...message, content } };
|
|
416
|
+
});
|
|
417
|
+
pi.on("tool_execution_start", (event) => {
|
|
418
|
+
running = event;
|
|
419
|
+
});
|
|
329
420
|
pi.on("tool_execution_end", (event) => {
|
|
421
|
+
running = undefined;
|
|
330
422
|
tools += 1;
|
|
331
423
|
if (!READ_ONLY.has(event.toolName)) nonReadonly += 1;
|
|
332
424
|
});
|
|
425
|
+
// Waiting-on-you: pi fires ui_prompt_start for every blocking extension
|
|
426
|
+
// dialog (select/confirm/input/editor/custom — bash guards, ask-user tools).
|
|
427
|
+
// A custom dialog carries no title, so name the call that raised it instead.
|
|
428
|
+
pi.on("ui_prompt_start", (event, ctx) => {
|
|
429
|
+
clearTimeout(promptTimer);
|
|
430
|
+
if (!agentRunning) return;
|
|
431
|
+
const command = running?.toolName === "bash" ? running.args?.command : undefined;
|
|
432
|
+
const body = event.title
|
|
433
|
+
?? (typeof command === "string" ? "$ " + command : undefined)
|
|
434
|
+
?? (running ? running.toolName + " is waiting for your answer" : "Waiting for your input");
|
|
435
|
+
promptTimer = setTimeout(() => {
|
|
436
|
+
promptTimer = undefined;
|
|
437
|
+
pushHigh(ctx.cwd, "pi asks: " + projectOf(ctx.cwd), body);
|
|
438
|
+
}, PROMPT_GRACE_MS);
|
|
439
|
+
promptTimer.unref?.();
|
|
440
|
+
});
|
|
441
|
+
pi.on("ui_prompt_end", () => {
|
|
442
|
+
clearTimeout(promptTimer);
|
|
443
|
+
promptTimer = undefined;
|
|
444
|
+
});
|
|
333
445
|
// Stop-equivalent: agent_settled fires once per user turn, after retries/compaction.
|
|
334
446
|
// Fire-and-forget: never block pi's turn-end on the notify network call.
|
|
335
447
|
pi.on("agent_settled", (_event, ctx) => {
|
|
336
|
-
|
|
448
|
+
agentRunning = false;
|
|
449
|
+
clearTimeout(promptTimer);
|
|
450
|
+
promptTimer = undefined;
|
|
451
|
+
// Esc (at the terminal, or a key sent from the phone): whoever stopped the
|
|
452
|
+
// turn is already looking at it.
|
|
453
|
+
if (stop?.reason === "aborted") return;
|
|
454
|
+
// A turn that died on a provider error is a blocker, not a completion.
|
|
455
|
+
if (stop?.reason === "error") {
|
|
456
|
+
pushHigh(ctx.cwd, "pi stopped: " + projectOf(ctx.cwd), stop.error || "The model request failed");
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
const child = spawn("sh", ["-c", ${notifyCmdLiteral(TURN_FACT_FLAGS + PUSH_SIGNAL_FLAG)}], { cwd: ctx.cwd, stdio: "ignore", detached: true });
|
|
337
460
|
child.on("error", () => {});
|
|
338
461
|
child.unref();
|
|
339
462
|
});
|
|
@@ -343,6 +466,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
343
466
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
344
467
|
tools = 0;
|
|
345
468
|
nonReadonly = 0;
|
|
469
|
+
marker = "none";
|
|
470
|
+
stop = undefined;
|
|
346
471
|
const out = await sh(${JSON.stringify(remoteHookCmd('pi'))}, ctx.cwd,
|
|
347
472
|
JSON.stringify({ prompt: event.prompt, cwd: ctx.cwd }));
|
|
348
473
|
try {
|