my-pi 0.1.0 → 0.1.2
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 +2 -0
- package/dist/{api-1ZXLxSgP.js → api-BVS4nqfD.js} +704 -101
- package/dist/api-BVS4nqfD.js.map +1 -0
- package/dist/api.js +1 -1
- package/dist/index.js +19 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/extensions/chain.test.ts +25 -0
- package/src/extensions/chain.ts +10 -1
- package/src/extensions/config.test.ts +9 -0
- package/src/extensions/config.ts +28 -1
- package/src/extensions/confirm-destructive.test.ts +157 -0
- package/src/extensions/confirm-destructive.ts +61 -0
- package/src/extensions/extensions.test.ts +114 -0
- package/src/extensions/extensions.ts +114 -108
- package/src/extensions/hooks-resolution.test.ts +246 -0
- package/src/extensions/hooks-resolution.ts +584 -0
- package/src/extensions/mcp.test.ts +30 -0
- package/src/extensions/mcp.ts +151 -23
- package/src/extensions/prompt-presets.test.ts +53 -0
- package/src/extensions/prompt-presets.ts +53 -21
- package/src/extensions/recall.test.ts +26 -0
- package/src/extensions/recall.ts +13 -2
- package/src/extensions/telemetry.test.ts +18 -0
- package/src/extensions/telemetry.ts +12 -2
- package/src/extensions/working-indicator-config.test.ts +68 -0
- package/src/extensions/working-indicator-config.ts +81 -0
- package/src/extensions/working-indicator.test.ts +38 -0
- package/src/extensions/working-indicator.ts +155 -0
- package/dist/api-1ZXLxSgP.js.map +0 -1
|
@@ -60,6 +60,10 @@ function parse_chain_yaml(raw) {
|
|
|
60
60
|
if (current && current_step) current.steps.push(current_step);
|
|
61
61
|
return chains;
|
|
62
62
|
}
|
|
63
|
+
function should_inject_chain_prompt(event) {
|
|
64
|
+
const selected_tools = event.systemPromptOptions?.selectedTools;
|
|
65
|
+
return !selected_tools || selected_tools.includes("run_chain");
|
|
66
|
+
}
|
|
63
67
|
function parse_agent_file(filePath) {
|
|
64
68
|
try {
|
|
65
69
|
const { frontmatter, body } = parseFrontmatter(readFileSync(filePath, "utf-8"));
|
|
@@ -290,6 +294,7 @@ async function chain(pi) {
|
|
|
290
294
|
});
|
|
291
295
|
pi.on("before_agent_start", async (event) => {
|
|
292
296
|
if (!active_chain || chains.length === 0) return {};
|
|
297
|
+
if (!should_inject_chain_prompt(event)) return {};
|
|
293
298
|
const flow = active_chain.steps.map((s) => s.agent).join(" -> ");
|
|
294
299
|
const step_list = active_chain.steps.map((s, i) => {
|
|
295
300
|
const desc = agents.get(s.agent.toLowerCase())?.description || "unknown";
|
|
@@ -340,7 +345,7 @@ Switch chains with /chain <name>.` };
|
|
|
340
345
|
}
|
|
341
346
|
//#endregion
|
|
342
347
|
//#region src/extensions/config.ts
|
|
343
|
-
const DEFAULT_CONFIG$
|
|
348
|
+
const DEFAULT_CONFIG$3 = {
|
|
344
349
|
version: 1,
|
|
345
350
|
enabled: {}
|
|
346
351
|
};
|
|
@@ -420,6 +425,31 @@ const BUILTIN_EXTENSIONS = [
|
|
|
420
425
|
"session",
|
|
421
426
|
"auto-name"
|
|
422
427
|
]
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
key: "confirm-destructive",
|
|
431
|
+
label: "Confirm destructive",
|
|
432
|
+
description: "Prompt before destructive session actions like clear, switch, and fork",
|
|
433
|
+
cli_flag: "--no-confirm-destructive",
|
|
434
|
+
aliases: ["confirm-destructive", "confirm"]
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
key: "hooks-resolution",
|
|
438
|
+
label: "Hooks resolution",
|
|
439
|
+
description: "Claude Code style PostToolUse hook compatibility from .claude, .rulesync, and .pi configs",
|
|
440
|
+
cli_flag: "--no-hooks",
|
|
441
|
+
aliases: ["hooks-resolution", "hooks"]
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
key: "working-indicator",
|
|
445
|
+
label: "Working indicator",
|
|
446
|
+
description: "Customizable streaming working indicator with /working-indicator command",
|
|
447
|
+
cli_flag: "--no-working-indicator",
|
|
448
|
+
aliases: [
|
|
449
|
+
"working-indicator",
|
|
450
|
+
"indicator",
|
|
451
|
+
"spinner"
|
|
452
|
+
]
|
|
423
453
|
}
|
|
424
454
|
];
|
|
425
455
|
function get_builtin_extensions_config_path() {
|
|
@@ -427,7 +457,7 @@ function get_builtin_extensions_config_path() {
|
|
|
427
457
|
}
|
|
428
458
|
function load_builtin_extensions_config() {
|
|
429
459
|
const path = get_builtin_extensions_config_path();
|
|
430
|
-
if (!existsSync(path)) return { ...DEFAULT_CONFIG$
|
|
460
|
+
if (!existsSync(path)) return { ...DEFAULT_CONFIG$3 };
|
|
431
461
|
try {
|
|
432
462
|
const raw = readFileSync(path, "utf-8");
|
|
433
463
|
const parsed = JSON.parse(raw);
|
|
@@ -441,7 +471,7 @@ function load_builtin_extensions_config() {
|
|
|
441
471
|
enabled
|
|
442
472
|
};
|
|
443
473
|
} catch {
|
|
444
|
-
return { ...DEFAULT_CONFIG$
|
|
474
|
+
return { ...DEFAULT_CONFIG$3 };
|
|
445
475
|
}
|
|
446
476
|
}
|
|
447
477
|
function save_builtin_extensions_config(config) {
|
|
@@ -483,6 +513,33 @@ function find_builtin_extension(query) {
|
|
|
483
513
|
].some((value) => value.toLowerCase() === normalized));
|
|
484
514
|
}
|
|
485
515
|
//#endregion
|
|
516
|
+
//#region src/extensions/confirm-destructive.ts
|
|
517
|
+
async function confirm_destructive(pi) {
|
|
518
|
+
pi.on("session_before_switch", async (event, ctx) => {
|
|
519
|
+
if (!ctx.hasUI) return;
|
|
520
|
+
if (event.reason === "new") {
|
|
521
|
+
if (!await ctx.ui.confirm("Clear session?", "This will delete all messages in the current session.")) {
|
|
522
|
+
ctx.ui.notify("Clear cancelled", "info");
|
|
523
|
+
return { cancel: true };
|
|
524
|
+
}
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
if (ctx.sessionManager.getEntries().some((e) => e.type === "message" && e.message.role === "user")) {
|
|
528
|
+
if (!await ctx.ui.confirm("Switch session?", "You have messages in the current session. Switch anyway?")) {
|
|
529
|
+
ctx.ui.notify("Switch cancelled", "info");
|
|
530
|
+
return { cancel: true };
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
pi.on("session_before_fork", async (event, ctx) => {
|
|
535
|
+
if (!ctx.hasUI) return;
|
|
536
|
+
if (await ctx.ui.select(`Fork from entry ${event.entryId.slice(0, 8)}?`, ["Yes, create fork", "No, stay in current session"]) !== "Yes, create fork") {
|
|
537
|
+
ctx.ui.notify("Fork cancelled", "info");
|
|
538
|
+
return { cancel: true };
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
//#endregion
|
|
486
543
|
//#region src/extensions/extensions.ts
|
|
487
544
|
const ENABLED$2 = "[x]";
|
|
488
545
|
const DISABLED$2 = "[ ]";
|
|
@@ -546,6 +603,66 @@ function save_extension_enabled(key, enabled) {
|
|
|
546
603
|
}
|
|
547
604
|
function create_extensions_extension(options = {}) {
|
|
548
605
|
const force_disabled = to_force_disabled_set(options.force_disabled);
|
|
606
|
+
async function show_manager(ctx) {
|
|
607
|
+
if (!ctx.hasUI) return false;
|
|
608
|
+
const states = resolve_builtin_extension_states(force_disabled);
|
|
609
|
+
const initial_enabled = new Set(states.filter((state) => state.saved_enabled).map((state) => state.key));
|
|
610
|
+
const current_enabled = new Set(initial_enabled);
|
|
611
|
+
await ctx.ui.custom((tui, theme, _kb, done) => {
|
|
612
|
+
const items = states.map(to_setting_item$1);
|
|
613
|
+
const container = new Container();
|
|
614
|
+
container.addChild({
|
|
615
|
+
render: () => {
|
|
616
|
+
const saved_enabled = current_enabled.size;
|
|
617
|
+
const saved_disabled = states.length - saved_enabled;
|
|
618
|
+
const enabled_now = [...current_enabled].filter((key) => !force_disabled.has(key)).length;
|
|
619
|
+
const disabled_now = states.length - enabled_now;
|
|
620
|
+
return [
|
|
621
|
+
theme.fg("accent", theme.bold("Built-in extensions")),
|
|
622
|
+
theme.fg("muted", `${saved_enabled} saved enabled • ${saved_disabled} saved disabled • ${enabled_now} enabled now • ${disabled_now} disabled now`),
|
|
623
|
+
""
|
|
624
|
+
];
|
|
625
|
+
},
|
|
626
|
+
invalidate: () => {}
|
|
627
|
+
});
|
|
628
|
+
const settings_list = new SettingsList(items, Math.min(Math.max(items.length + 4, 8), 16), {
|
|
629
|
+
cursor: theme.fg("accent", "›"),
|
|
630
|
+
label: (text, selected) => selected ? theme.fg("accent", text) : text,
|
|
631
|
+
value: (text, selected) => {
|
|
632
|
+
const color = text === ENABLED$2 ? "success" : "dim";
|
|
633
|
+
const rendered = theme.fg(color, text);
|
|
634
|
+
return selected ? theme.bold(theme.fg("accent", rendered)) : rendered;
|
|
635
|
+
},
|
|
636
|
+
description: (text) => theme.fg("muted", text),
|
|
637
|
+
hint: (text) => theme.fg("dim", text)
|
|
638
|
+
}, (id, new_value) => {
|
|
639
|
+
const key = id;
|
|
640
|
+
const enabled = new_value === ENABLED$2;
|
|
641
|
+
if (enabled) current_enabled.add(key);
|
|
642
|
+
else current_enabled.delete(key);
|
|
643
|
+
save_extension_enabled(key, enabled);
|
|
644
|
+
}, () => done(void 0), { enableSearch: true });
|
|
645
|
+
container.addChild(settings_list);
|
|
646
|
+
container.addChild(new Text(theme.fg("dim", "esc close • search filters • changes save immediately • CLI --no-* flags still win in this process"), 0, 1));
|
|
647
|
+
return {
|
|
648
|
+
render(width) {
|
|
649
|
+
return container.render(width);
|
|
650
|
+
},
|
|
651
|
+
invalidate() {
|
|
652
|
+
container.invalidate();
|
|
653
|
+
},
|
|
654
|
+
handleInput(data) {
|
|
655
|
+
settings_list.handleInput(data);
|
|
656
|
+
tui.requestRender();
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
});
|
|
660
|
+
if (!sets_equal$2(initial_enabled, current_enabled)) {
|
|
661
|
+
ctx.ui.notify(force_disabled.size > 0 ? "Reloading to apply updated built-in extensions. CLI --no-* flags still force-disable some extensions in this process." : "Reloading to apply updated built-in extensions...", "info");
|
|
662
|
+
await ctx.reload();
|
|
663
|
+
}
|
|
664
|
+
return true;
|
|
665
|
+
}
|
|
549
666
|
return async function extensions(pi) {
|
|
550
667
|
const subs = [
|
|
551
668
|
"list",
|
|
@@ -577,65 +694,8 @@ function create_extensions_extension(options = {}) {
|
|
|
577
694
|
},
|
|
578
695
|
handler: async (args, ctx) => {
|
|
579
696
|
const trimmed = args.trim();
|
|
580
|
-
if (!trimmed
|
|
581
|
-
|
|
582
|
-
const initial_enabled = new Set(states.filter((state) => state.saved_enabled).map((state) => state.key));
|
|
583
|
-
const current_enabled = new Set(initial_enabled);
|
|
584
|
-
await ctx.ui.custom((tui, theme, _kb, done) => {
|
|
585
|
-
const items = states.map(to_setting_item$1);
|
|
586
|
-
const container = new Container();
|
|
587
|
-
container.addChild({
|
|
588
|
-
render: () => {
|
|
589
|
-
const saved_enabled = current_enabled.size;
|
|
590
|
-
const saved_disabled = states.length - saved_enabled;
|
|
591
|
-
const enabled_now = [...current_enabled].filter((key) => !force_disabled.has(key)).length;
|
|
592
|
-
const disabled_now = states.length - enabled_now;
|
|
593
|
-
return [
|
|
594
|
-
theme.fg("accent", theme.bold("Built-in extensions")),
|
|
595
|
-
theme.fg("muted", `${saved_enabled} saved enabled • ${saved_disabled} saved disabled • ${enabled_now} enabled now • ${disabled_now} disabled now`),
|
|
596
|
-
""
|
|
597
|
-
];
|
|
598
|
-
},
|
|
599
|
-
invalidate: () => {}
|
|
600
|
-
});
|
|
601
|
-
const settings_list = new SettingsList(items, Math.min(Math.max(items.length + 4, 8), 16), {
|
|
602
|
-
cursor: theme.fg("accent", "›"),
|
|
603
|
-
label: (text, selected) => selected ? theme.fg("accent", text) : text,
|
|
604
|
-
value: (text, selected) => {
|
|
605
|
-
const color = text === ENABLED$2 ? "success" : "dim";
|
|
606
|
-
const rendered = theme.fg(color, text);
|
|
607
|
-
return selected ? theme.bold(theme.fg("accent", rendered)) : rendered;
|
|
608
|
-
},
|
|
609
|
-
description: (text) => theme.fg("muted", text),
|
|
610
|
-
hint: (text) => theme.fg("dim", text)
|
|
611
|
-
}, (id, new_value) => {
|
|
612
|
-
const key = id;
|
|
613
|
-
const enabled = new_value === ENABLED$2;
|
|
614
|
-
if (enabled) current_enabled.add(key);
|
|
615
|
-
else current_enabled.delete(key);
|
|
616
|
-
save_extension_enabled(key, enabled);
|
|
617
|
-
}, () => done(void 0), { enableSearch: true });
|
|
618
|
-
container.addChild(settings_list);
|
|
619
|
-
container.addChild(new Text(theme.fg("dim", "esc close • search filters • changes save immediately • CLI --no-* flags still win in this process"), 0, 1));
|
|
620
|
-
return {
|
|
621
|
-
render(width) {
|
|
622
|
-
return container.render(width);
|
|
623
|
-
},
|
|
624
|
-
invalidate() {
|
|
625
|
-
container.invalidate();
|
|
626
|
-
},
|
|
627
|
-
handleInput(data) {
|
|
628
|
-
settings_list.handleInput(data);
|
|
629
|
-
tui.requestRender();
|
|
630
|
-
}
|
|
631
|
-
};
|
|
632
|
-
});
|
|
633
|
-
if (!sets_equal$2(initial_enabled, current_enabled)) {
|
|
634
|
-
ctx.ui.notify(force_disabled.size > 0 ? "Reloading to apply updated built-in extensions. CLI --no-* flags still force-disable some extensions in this process." : "Reloading to apply updated built-in extensions...", "info");
|
|
635
|
-
await ctx.reload();
|
|
636
|
-
return;
|
|
637
|
-
}
|
|
638
|
-
return;
|
|
697
|
+
if (!trimmed) {
|
|
698
|
+
if (await show_manager(ctx)) return;
|
|
639
699
|
}
|
|
640
700
|
const [sub, ...rest] = (trimmed || "list").split(/\s+/);
|
|
641
701
|
const arg = rest.join(" ");
|
|
@@ -648,6 +708,7 @@ function create_extensions_extension(options = {}) {
|
|
|
648
708
|
case "disable":
|
|
649
709
|
case "toggle": {
|
|
650
710
|
if (!arg) {
|
|
711
|
+
if (await show_manager(ctx)) return;
|
|
651
712
|
ctx.ui.notify(`Usage: /extensions ${sub} <key>`, "warning");
|
|
652
713
|
return;
|
|
653
714
|
}
|
|
@@ -896,6 +957,312 @@ async function handoff(pi) {
|
|
|
896
957
|
});
|
|
897
958
|
}
|
|
898
959
|
//#endregion
|
|
960
|
+
//#region src/extensions/hooks-resolution.ts
|
|
961
|
+
const HOOK_TIMEOUT_MS = 600 * 1e3;
|
|
962
|
+
function is_file(path) {
|
|
963
|
+
try {
|
|
964
|
+
return statSync(path).isFile();
|
|
965
|
+
} catch {
|
|
966
|
+
return false;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
function as_record(value) {
|
|
970
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
971
|
+
return value;
|
|
972
|
+
}
|
|
973
|
+
function walk_up_directories(start_dir, stop_dir) {
|
|
974
|
+
const directories = [];
|
|
975
|
+
const has_stop_dir = stop_dir !== void 0;
|
|
976
|
+
let current = resolve(start_dir);
|
|
977
|
+
let parent = dirname(current);
|
|
978
|
+
let reached_stop_dir = has_stop_dir && current === stop_dir;
|
|
979
|
+
let reached_filesystem_root = parent === current;
|
|
980
|
+
directories.push(current);
|
|
981
|
+
while (!reached_stop_dir && !reached_filesystem_root) {
|
|
982
|
+
current = parent;
|
|
983
|
+
parent = dirname(current);
|
|
984
|
+
reached_stop_dir = has_stop_dir && current === stop_dir;
|
|
985
|
+
reached_filesystem_root = parent === current;
|
|
986
|
+
directories.push(current);
|
|
987
|
+
}
|
|
988
|
+
return directories;
|
|
989
|
+
}
|
|
990
|
+
function find_nearest_git_root(start_dir) {
|
|
991
|
+
for (const directory of walk_up_directories(start_dir)) if (existsSync(join(directory, ".git"))) return directory;
|
|
992
|
+
}
|
|
993
|
+
function has_hooks_config(directory) {
|
|
994
|
+
return is_file(join(directory, ".claude", "settings.json")) || is_file(join(directory, ".rulesync", "hooks.json")) || is_file(join(directory, ".pi", "hooks.json"));
|
|
995
|
+
}
|
|
996
|
+
function find_project_dir(cwd) {
|
|
997
|
+
const git_root = find_nearest_git_root(cwd);
|
|
998
|
+
for (const directory of walk_up_directories(cwd, git_root)) if (has_hooks_config(directory)) return directory;
|
|
999
|
+
return git_root ?? resolve(cwd);
|
|
1000
|
+
}
|
|
1001
|
+
function read_json_file(path) {
|
|
1002
|
+
if (!is_file(path)) return void 0;
|
|
1003
|
+
try {
|
|
1004
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
1005
|
+
} catch {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
function resolve_hook_command(command, project_dir) {
|
|
1010
|
+
return command.replace(/\$CLAUDE_PROJECT_DIR\b/g, project_dir);
|
|
1011
|
+
}
|
|
1012
|
+
function compile_matcher(matcher_text) {
|
|
1013
|
+
if (matcher_text === void 0) return void 0;
|
|
1014
|
+
try {
|
|
1015
|
+
return new RegExp(matcher_text);
|
|
1016
|
+
} catch {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
function create_hook(event_name, matcher_text, command, source, project_dir) {
|
|
1021
|
+
const matcher = compile_matcher(matcher_text);
|
|
1022
|
+
if (matcher_text !== void 0 && matcher === void 0) return void 0;
|
|
1023
|
+
return {
|
|
1024
|
+
event_name,
|
|
1025
|
+
matcher,
|
|
1026
|
+
matcher_text,
|
|
1027
|
+
command: resolve_hook_command(command, project_dir),
|
|
1028
|
+
source
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
function get_hook_entries(hooks_record, event_name) {
|
|
1032
|
+
const keys = event_name === "PostToolUse" ? ["PostToolUse", "postToolUse"] : ["PostToolUseFailure", "postToolUseFailure"];
|
|
1033
|
+
for (const key of keys) {
|
|
1034
|
+
const value = hooks_record[key];
|
|
1035
|
+
if (Array.isArray(value)) return value;
|
|
1036
|
+
}
|
|
1037
|
+
return [];
|
|
1038
|
+
}
|
|
1039
|
+
function parse_claude_settings_hooks(config, source, project_dir) {
|
|
1040
|
+
const root = as_record(config);
|
|
1041
|
+
const hooks_root = root ? as_record(root.hooks) : void 0;
|
|
1042
|
+
if (!hooks_root) return [];
|
|
1043
|
+
const hooks = [];
|
|
1044
|
+
for (const event_name of ["PostToolUse", "PostToolUseFailure"]) {
|
|
1045
|
+
const entries = get_hook_entries(hooks_root, event_name);
|
|
1046
|
+
for (const entry of entries) {
|
|
1047
|
+
const entry_record = as_record(entry);
|
|
1048
|
+
if (!entry_record || !Array.isArray(entry_record.hooks)) continue;
|
|
1049
|
+
const matcher_text = typeof entry_record.matcher === "string" ? entry_record.matcher : void 0;
|
|
1050
|
+
for (const nested_hook of entry_record.hooks) {
|
|
1051
|
+
const nested_record = as_record(nested_hook);
|
|
1052
|
+
if (!nested_record) continue;
|
|
1053
|
+
if (nested_record.type !== "command") continue;
|
|
1054
|
+
if (typeof nested_record.command !== "string") continue;
|
|
1055
|
+
const hook = create_hook(event_name, matcher_text, nested_record.command, source, project_dir);
|
|
1056
|
+
if (hook) hooks.push(hook);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
return hooks;
|
|
1061
|
+
}
|
|
1062
|
+
function parse_simple_hooks_file(config, source, project_dir) {
|
|
1063
|
+
const root = as_record(config);
|
|
1064
|
+
const hooks_root = root ? as_record(root.hooks) : void 0;
|
|
1065
|
+
if (!hooks_root) return [];
|
|
1066
|
+
const hooks = [];
|
|
1067
|
+
for (const event_name of ["PostToolUse", "PostToolUseFailure"]) {
|
|
1068
|
+
const entries = get_hook_entries(hooks_root, event_name);
|
|
1069
|
+
for (const entry of entries) {
|
|
1070
|
+
const entry_record = as_record(entry);
|
|
1071
|
+
if (!entry_record || typeof entry_record.command !== "string") continue;
|
|
1072
|
+
const hook = create_hook(event_name, typeof entry_record.matcher === "string" ? entry_record.matcher : void 0, entry_record.command, source, project_dir);
|
|
1073
|
+
if (hook) hooks.push(hook);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
return hooks;
|
|
1077
|
+
}
|
|
1078
|
+
function load_hooks(cwd) {
|
|
1079
|
+
const project_dir = find_project_dir(cwd);
|
|
1080
|
+
const hooks = [];
|
|
1081
|
+
const claude_settings_path = join(project_dir, ".claude", "settings.json");
|
|
1082
|
+
const rulesync_hooks_path = join(project_dir, ".rulesync", "hooks.json");
|
|
1083
|
+
const pi_hooks_path = join(project_dir, ".pi", "hooks.json");
|
|
1084
|
+
const claude_settings = read_json_file(claude_settings_path);
|
|
1085
|
+
if (claude_settings !== void 0) hooks.push(...parse_claude_settings_hooks(claude_settings, claude_settings_path, project_dir));
|
|
1086
|
+
const rulesync_hooks = read_json_file(rulesync_hooks_path);
|
|
1087
|
+
if (rulesync_hooks !== void 0) hooks.push(...parse_simple_hooks_file(rulesync_hooks, rulesync_hooks_path, project_dir));
|
|
1088
|
+
const pi_hooks = read_json_file(pi_hooks_path);
|
|
1089
|
+
if (pi_hooks !== void 0) hooks.push(...parse_simple_hooks_file(pi_hooks, pi_hooks_path, project_dir));
|
|
1090
|
+
return {
|
|
1091
|
+
project_dir,
|
|
1092
|
+
hooks
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
function to_claude_tool_name(tool_name) {
|
|
1096
|
+
if (tool_name === "ls") return "LS";
|
|
1097
|
+
if (tool_name.length === 0) return tool_name;
|
|
1098
|
+
return tool_name[0].toUpperCase() + tool_name.slice(1);
|
|
1099
|
+
}
|
|
1100
|
+
function matches_hook(hook, tool_name) {
|
|
1101
|
+
if (!hook.matcher) return true;
|
|
1102
|
+
const claude_tool_name = to_claude_tool_name(tool_name);
|
|
1103
|
+
hook.matcher.lastIndex = 0;
|
|
1104
|
+
if (hook.matcher.test(tool_name)) return true;
|
|
1105
|
+
hook.matcher.lastIndex = 0;
|
|
1106
|
+
return hook.matcher.test(claude_tool_name);
|
|
1107
|
+
}
|
|
1108
|
+
function extract_text_content(content) {
|
|
1109
|
+
if (!Array.isArray(content)) return "";
|
|
1110
|
+
const parts = [];
|
|
1111
|
+
for (const item of content) {
|
|
1112
|
+
if (!item || typeof item !== "object") continue;
|
|
1113
|
+
const item_record = item;
|
|
1114
|
+
if (item_record.type === "text" && typeof item_record.text === "string") parts.push(item_record.text);
|
|
1115
|
+
}
|
|
1116
|
+
return parts.join("\n");
|
|
1117
|
+
}
|
|
1118
|
+
function normalize_tool_input(input) {
|
|
1119
|
+
const normalized = { ...input };
|
|
1120
|
+
const path_value = typeof input.path === "string" ? input.path : void 0;
|
|
1121
|
+
if (path_value !== void 0) {
|
|
1122
|
+
normalized.file_path = path_value;
|
|
1123
|
+
normalized.filePath = path_value;
|
|
1124
|
+
}
|
|
1125
|
+
return normalized;
|
|
1126
|
+
}
|
|
1127
|
+
function build_tool_response(event, normalized_input) {
|
|
1128
|
+
const response = {
|
|
1129
|
+
is_error: event.isError,
|
|
1130
|
+
isError: event.isError,
|
|
1131
|
+
content: event.content,
|
|
1132
|
+
text: extract_text_content(event.content),
|
|
1133
|
+
details: event.details ?? null
|
|
1134
|
+
};
|
|
1135
|
+
const file_path = typeof normalized_input.file_path === "string" ? normalized_input.file_path : void 0;
|
|
1136
|
+
if (file_path !== void 0) {
|
|
1137
|
+
response.file_path = file_path;
|
|
1138
|
+
response.filePath = file_path;
|
|
1139
|
+
}
|
|
1140
|
+
return response;
|
|
1141
|
+
}
|
|
1142
|
+
function build_hook_payload(event, event_name, ctx, project_dir) {
|
|
1143
|
+
const normalized_input = normalize_tool_input(event.input);
|
|
1144
|
+
return {
|
|
1145
|
+
session_id: ctx.sessionManager.getSessionFile() ?? "ephemeral",
|
|
1146
|
+
cwd: ctx.cwd,
|
|
1147
|
+
claude_project_dir: project_dir,
|
|
1148
|
+
hook_event_name: event_name,
|
|
1149
|
+
tool_name: to_claude_tool_name(event.toolName),
|
|
1150
|
+
tool_call_id: event.toolCallId,
|
|
1151
|
+
tool_input: normalized_input,
|
|
1152
|
+
tool_response: build_tool_response(event, normalized_input)
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
async function run_command_hook(command, cwd, payload) {
|
|
1156
|
+
return await new Promise((resolve) => {
|
|
1157
|
+
const started_at = Date.now();
|
|
1158
|
+
const child = spawn("bash", ["-lc", command], {
|
|
1159
|
+
cwd,
|
|
1160
|
+
env: {
|
|
1161
|
+
...process.env,
|
|
1162
|
+
CLAUDE_PROJECT_DIR: cwd
|
|
1163
|
+
},
|
|
1164
|
+
stdio: [
|
|
1165
|
+
"pipe",
|
|
1166
|
+
"pipe",
|
|
1167
|
+
"pipe"
|
|
1168
|
+
]
|
|
1169
|
+
});
|
|
1170
|
+
let stdout = "";
|
|
1171
|
+
let stderr = "";
|
|
1172
|
+
let timed_out = false;
|
|
1173
|
+
let resolved = false;
|
|
1174
|
+
const finish = (code) => {
|
|
1175
|
+
if (resolved) return;
|
|
1176
|
+
resolved = true;
|
|
1177
|
+
resolve({
|
|
1178
|
+
code,
|
|
1179
|
+
stdout,
|
|
1180
|
+
stderr,
|
|
1181
|
+
elapsed_ms: Date.now() - started_at,
|
|
1182
|
+
timed_out
|
|
1183
|
+
});
|
|
1184
|
+
};
|
|
1185
|
+
const timeout = setTimeout(() => {
|
|
1186
|
+
timed_out = true;
|
|
1187
|
+
child.kill("SIGTERM");
|
|
1188
|
+
setTimeout(() => {
|
|
1189
|
+
child.kill("SIGKILL");
|
|
1190
|
+
}, 1e3).unref?.();
|
|
1191
|
+
}, HOOK_TIMEOUT_MS);
|
|
1192
|
+
timeout.unref?.();
|
|
1193
|
+
child.stdout.on("data", (chunk) => {
|
|
1194
|
+
stdout += chunk.toString("utf8");
|
|
1195
|
+
});
|
|
1196
|
+
child.stderr.on("data", (chunk) => {
|
|
1197
|
+
stderr += chunk.toString("utf8");
|
|
1198
|
+
});
|
|
1199
|
+
child.on("error", (error) => {
|
|
1200
|
+
clearTimeout(timeout);
|
|
1201
|
+
stderr += `${error.message}\n`;
|
|
1202
|
+
finish(-1);
|
|
1203
|
+
});
|
|
1204
|
+
child.on("close", (code) => {
|
|
1205
|
+
clearTimeout(timeout);
|
|
1206
|
+
finish(code ?? -1);
|
|
1207
|
+
});
|
|
1208
|
+
try {
|
|
1209
|
+
child.stdin.write(JSON.stringify(payload));
|
|
1210
|
+
child.stdin.end();
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
stderr += `${error instanceof Error ? error.message : String(error)}\n`;
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
function hook_event_name_for_result(event) {
|
|
1217
|
+
return event.isError ? "PostToolUseFailure" : "PostToolUse";
|
|
1218
|
+
}
|
|
1219
|
+
function format_duration$1(elapsed_ms) {
|
|
1220
|
+
if (elapsed_ms < 1e3) return `${elapsed_ms}ms`;
|
|
1221
|
+
return `${(elapsed_ms / 1e3).toFixed(1)}s`;
|
|
1222
|
+
}
|
|
1223
|
+
function hook_name(command) {
|
|
1224
|
+
const sh_path_match = command.match(/[^\s|;&]+\.sh\b/);
|
|
1225
|
+
if (sh_path_match) return basename(sh_path_match[0]);
|
|
1226
|
+
return basename(command.trim().split(/\s+/)[0] ?? "hook");
|
|
1227
|
+
}
|
|
1228
|
+
function create_hooks_resolution_extension(options = {}) {
|
|
1229
|
+
const load_hooks_impl = options.load_hooks ?? load_hooks;
|
|
1230
|
+
const run_command_hook_impl = options.run_command_hook ?? run_command_hook;
|
|
1231
|
+
return async function hooks_resolution(pi) {
|
|
1232
|
+
let state = {
|
|
1233
|
+
project_dir: process.cwd(),
|
|
1234
|
+
hooks: []
|
|
1235
|
+
};
|
|
1236
|
+
const refresh_hooks = (cwd) => {
|
|
1237
|
+
state = load_hooks_impl(cwd);
|
|
1238
|
+
};
|
|
1239
|
+
pi.on("session_start", (_event, ctx) => {
|
|
1240
|
+
refresh_hooks(ctx.cwd);
|
|
1241
|
+
});
|
|
1242
|
+
pi.on("tool_result", async (event, ctx) => {
|
|
1243
|
+
if (state.hooks.length === 0) return;
|
|
1244
|
+
const event_name = hook_event_name_for_result(event);
|
|
1245
|
+
const matching_hooks = state.hooks.filter((hook) => hook.event_name === event_name && matches_hook(hook, event.toolName));
|
|
1246
|
+
if (matching_hooks.length === 0) return;
|
|
1247
|
+
const payload = build_hook_payload(event, event_name, ctx, state.project_dir);
|
|
1248
|
+
const executed_commands = /* @__PURE__ */ new Set();
|
|
1249
|
+
for (const hook of matching_hooks) {
|
|
1250
|
+
if (executed_commands.has(hook.command)) continue;
|
|
1251
|
+
executed_commands.add(hook.command);
|
|
1252
|
+
const result = await run_command_hook_impl(hook.command, state.project_dir, payload);
|
|
1253
|
+
const name = hook_name(hook.command);
|
|
1254
|
+
const duration = format_duration$1(result.elapsed_ms);
|
|
1255
|
+
if (ctx.hasUI) if (result.code === 0) ctx.ui.notify(`Hook \`${name}\` ran (${duration})`, "info");
|
|
1256
|
+
else {
|
|
1257
|
+
const error_line = result.stderr.trim() || result.stdout.trim() || `exit code ${result.code}`;
|
|
1258
|
+
ctx.ui.notify(`Hook \`${name}\` failed (${duration}): ${error_line}`, "warning");
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
var hooks_resolution_default = create_hooks_resolution_extension();
|
|
1265
|
+
//#endregion
|
|
899
1266
|
//#region src/lsp/client.ts
|
|
900
1267
|
var LspClientStartError = class extends Error {
|
|
901
1268
|
command;
|
|
@@ -2164,6 +2531,14 @@ function load_mcp_config(cwd) {
|
|
|
2164
2531
|
}
|
|
2165
2532
|
//#endregion
|
|
2166
2533
|
//#region src/extensions/mcp.ts
|
|
2534
|
+
function create_server_states(configs) {
|
|
2535
|
+
return new Map(configs.map((config) => [config.name, {
|
|
2536
|
+
config,
|
|
2537
|
+
tool_names: [],
|
|
2538
|
+
enabled: true,
|
|
2539
|
+
status: "disconnected"
|
|
2540
|
+
}]));
|
|
2541
|
+
}
|
|
2167
2542
|
function remove_server_tools_from_active(pi, tool_names) {
|
|
2168
2543
|
const tool_set = new Set(tool_names);
|
|
2169
2544
|
pi.setActiveTools(pi.getActiveTools().filter((tool) => !tool_set.has(tool)));
|
|
@@ -2176,16 +2551,56 @@ function format_server_status(state) {
|
|
|
2176
2551
|
default: return state.enabled ? "not connected yet" : "disabled";
|
|
2177
2552
|
}
|
|
2178
2553
|
}
|
|
2554
|
+
function count_pending_enabled_servers(servers) {
|
|
2555
|
+
return Array.from(servers.values()).filter((state) => state.enabled && state.status !== "connected").length;
|
|
2556
|
+
}
|
|
2557
|
+
function update_mcp_status(ctx, servers) {
|
|
2558
|
+
if (servers.size === 0) {
|
|
2559
|
+
ctx.ui.setStatus("mcp", void 0);
|
|
2560
|
+
return;
|
|
2561
|
+
}
|
|
2562
|
+
const states = Array.from(servers.values());
|
|
2563
|
+
const enabled = states.filter((state) => state.enabled).length;
|
|
2564
|
+
const connected = states.filter((state) => state.enabled && state.status === "connected").length;
|
|
2565
|
+
const connecting = states.filter((state) => state.enabled && state.status === "connecting").length;
|
|
2566
|
+
const failed = states.filter((state) => state.enabled && state.status === "failed").length;
|
|
2567
|
+
const fragments = [`MCP ${connected}/${enabled} connected`];
|
|
2568
|
+
if (connecting > 0) fragments.push(`${connecting} connecting`);
|
|
2569
|
+
if (failed > 0) fragments.push(`${failed} failed`);
|
|
2570
|
+
ctx.ui.setStatus("mcp", ctx.ui.theme.fg("dim", fragments.join(" · ")));
|
|
2571
|
+
}
|
|
2572
|
+
function set_connect_feedback(ctx, pending_server_count) {
|
|
2573
|
+
const label = pending_server_count === 1 ? "Connecting 1 MCP server..." : `Connecting ${pending_server_count} MCP servers...`;
|
|
2574
|
+
ctx.ui.setWorkingMessage(label);
|
|
2575
|
+
ctx.ui.setWorkingIndicator({
|
|
2576
|
+
frames: [
|
|
2577
|
+
ctx.ui.theme.fg("dim", "·"),
|
|
2578
|
+
ctx.ui.theme.fg("muted", "•"),
|
|
2579
|
+
ctx.ui.theme.fg("accent", "●"),
|
|
2580
|
+
ctx.ui.theme.fg("muted", "•")
|
|
2581
|
+
],
|
|
2582
|
+
intervalMs: 120
|
|
2583
|
+
});
|
|
2584
|
+
ctx.ui.setStatus("mcp", ctx.ui.theme.fg("dim", label));
|
|
2585
|
+
return () => {
|
|
2586
|
+
ctx.ui.setWorkingMessage();
|
|
2587
|
+
ctx.ui.setWorkingIndicator();
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
function should_wait_for_mcp_connections(event) {
|
|
2591
|
+
const selected_tools = event.systemPromptOptions?.selectedTools;
|
|
2592
|
+
return !selected_tools || selected_tools.some((tool) => tool.startsWith("mcp__"));
|
|
2593
|
+
}
|
|
2179
2594
|
async function mcp(pi) {
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
config,
|
|
2183
|
-
tool_names: [],
|
|
2184
|
-
enabled: true,
|
|
2185
|
-
status: "disconnected"
|
|
2186
|
-
}]));
|
|
2595
|
+
let initialized_cwd = null;
|
|
2596
|
+
let servers = /* @__PURE__ */ new Map();
|
|
2187
2597
|
const registered_tool_names = /* @__PURE__ */ new Set();
|
|
2188
|
-
const
|
|
2598
|
+
const ensure_servers = (cwd) => {
|
|
2599
|
+
if (initialized_cwd !== null) return;
|
|
2600
|
+
servers = create_server_states(load_mcp_config(cwd));
|
|
2601
|
+
initialized_cwd = cwd;
|
|
2602
|
+
};
|
|
2603
|
+
const connect_server = async (state, ctx) => {
|
|
2189
2604
|
if (state.status === "connected") return;
|
|
2190
2605
|
if (state.connect_promise) {
|
|
2191
2606
|
await state.connect_promise;
|
|
@@ -2194,6 +2609,7 @@ async function mcp(pi) {
|
|
|
2194
2609
|
state.connect_promise = (async () => {
|
|
2195
2610
|
state.status = "connecting";
|
|
2196
2611
|
state.error = void 0;
|
|
2612
|
+
if (ctx) update_mcp_status(ctx, servers);
|
|
2197
2613
|
const client = new McpClient(state.config);
|
|
2198
2614
|
try {
|
|
2199
2615
|
await client.connect();
|
|
@@ -2237,19 +2653,39 @@ async function mcp(pi) {
|
|
|
2237
2653
|
throw error;
|
|
2238
2654
|
} finally {
|
|
2239
2655
|
state.connect_promise = void 0;
|
|
2656
|
+
if (ctx) update_mcp_status(ctx, servers);
|
|
2240
2657
|
}
|
|
2241
2658
|
})();
|
|
2242
2659
|
await state.connect_promise;
|
|
2243
2660
|
};
|
|
2244
2661
|
const connect_all_servers = async (options = {}) => {
|
|
2245
|
-
await Promise.allSettled(Array.from(servers.values()).filter((state) => state.enabled).filter((state) => options.include_failed || state.status !== "failed").map((state) => connect_server(state)));
|
|
2662
|
+
await Promise.allSettled(Array.from(servers.values()).filter((state) => state.enabled).filter((state) => options.include_failed || state.status !== "failed").map((state) => connect_server(state, options.ctx)));
|
|
2663
|
+
if (options.ctx) update_mcp_status(options.ctx, servers);
|
|
2246
2664
|
};
|
|
2247
|
-
pi.on("session_start", async () => {
|
|
2248
|
-
|
|
2665
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
2666
|
+
ensure_servers(ctx.cwd);
|
|
2667
|
+
update_mcp_status(ctx, servers);
|
|
2668
|
+
connect_all_servers({ ctx });
|
|
2249
2669
|
});
|
|
2250
|
-
pi.on("before_agent_start", async (event) => {
|
|
2251
|
-
|
|
2252
|
-
|
|
2670
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
2671
|
+
ensure_servers(ctx.cwd);
|
|
2672
|
+
if (!should_wait_for_mcp_connections(event)) {
|
|
2673
|
+
connect_all_servers({ ctx });
|
|
2674
|
+
return event;
|
|
2675
|
+
}
|
|
2676
|
+
const pending_server_count = count_pending_enabled_servers(servers);
|
|
2677
|
+
if (pending_server_count === 0) {
|
|
2678
|
+
update_mcp_status(ctx, servers);
|
|
2679
|
+
return event;
|
|
2680
|
+
}
|
|
2681
|
+
const restore_feedback = set_connect_feedback(ctx, pending_server_count);
|
|
2682
|
+
try {
|
|
2683
|
+
await connect_all_servers({ ctx });
|
|
2684
|
+
return event;
|
|
2685
|
+
} finally {
|
|
2686
|
+
restore_feedback();
|
|
2687
|
+
update_mcp_status(ctx, servers);
|
|
2688
|
+
}
|
|
2253
2689
|
});
|
|
2254
2690
|
pi.registerCommand("mcp", {
|
|
2255
2691
|
description: "Manage MCP servers (list, enable, disable)",
|
|
@@ -2273,6 +2709,7 @@ async function mcp(pi) {
|
|
|
2273
2709
|
return null;
|
|
2274
2710
|
},
|
|
2275
2711
|
handler: async (args, ctx) => {
|
|
2712
|
+
ensure_servers(ctx.cwd);
|
|
2276
2713
|
const [sub, ...rest] = args.trim().split(/\s+/);
|
|
2277
2714
|
const name = rest.join(" ");
|
|
2278
2715
|
switch (sub || "list") {
|
|
@@ -2283,6 +2720,7 @@ async function mcp(pi) {
|
|
|
2283
2720
|
}
|
|
2284
2721
|
const lines = [];
|
|
2285
2722
|
for (const [sname, state] of servers.entries()) lines.push(`${sname} (${format_server_status(state)}) — ${state.tool_names.length} tools${state.error ? ` — ${state.error}` : ""}`);
|
|
2723
|
+
update_mcp_status(ctx, servers);
|
|
2286
2724
|
ctx.ui.notify(lines.join("\n"));
|
|
2287
2725
|
break;
|
|
2288
2726
|
}
|
|
@@ -2299,7 +2737,8 @@ async function mcp(pi) {
|
|
|
2299
2737
|
server.enabled = true;
|
|
2300
2738
|
if (server.status === "connected") {
|
|
2301
2739
|
const active = pi.getActiveTools();
|
|
2302
|
-
pi.setActiveTools([...active, ...server.tool_names]);
|
|
2740
|
+
pi.setActiveTools([...new Set([...active, ...server.tool_names])]);
|
|
2741
|
+
update_mcp_status(ctx, servers);
|
|
2303
2742
|
ctx.ui.notify(`Enabled ${name}`);
|
|
2304
2743
|
return;
|
|
2305
2744
|
}
|
|
@@ -2307,7 +2746,8 @@ async function mcp(pi) {
|
|
|
2307
2746
|
server.status = "disconnected";
|
|
2308
2747
|
server.error = void 0;
|
|
2309
2748
|
}
|
|
2310
|
-
|
|
2749
|
+
update_mcp_status(ctx, servers);
|
|
2750
|
+
connect_server(server, ctx);
|
|
2311
2751
|
ctx.ui.notify(`Enabling ${name} and connecting in background`);
|
|
2312
2752
|
break;
|
|
2313
2753
|
}
|
|
@@ -2323,6 +2763,7 @@ async function mcp(pi) {
|
|
|
2323
2763
|
}
|
|
2324
2764
|
server.enabled = false;
|
|
2325
2765
|
remove_server_tools_from_active(pi, server.tool_names);
|
|
2766
|
+
update_mcp_status(ctx, servers);
|
|
2326
2767
|
ctx.ui.notify(`Disabled ${name}`);
|
|
2327
2768
|
break;
|
|
2328
2769
|
}
|
|
@@ -2330,13 +2771,14 @@ async function mcp(pi) {
|
|
|
2330
2771
|
}
|
|
2331
2772
|
}
|
|
2332
2773
|
});
|
|
2333
|
-
pi.on("session_shutdown", async () => {
|
|
2774
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
2334
2775
|
await Promise.allSettled(Array.from(servers.values()).map(async (server) => {
|
|
2335
2776
|
await server.connect_promise?.catch(() => {});
|
|
2336
2777
|
await server.client?.disconnect();
|
|
2337
2778
|
server.client = void 0;
|
|
2338
2779
|
if (server.status !== "failed") server.status = "disconnected";
|
|
2339
2780
|
}));
|
|
2781
|
+
ctx.ui.setStatus("mcp", void 0);
|
|
2340
2782
|
});
|
|
2341
2783
|
}
|
|
2342
2784
|
//#endregion
|
|
@@ -2608,6 +3050,24 @@ function format_token_count(count) {
|
|
|
2608
3050
|
if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
|
|
2609
3051
|
return `${Math.round(count / 1e6)}M`;
|
|
2610
3052
|
}
|
|
3053
|
+
function render_footer_status_line(theme, width, left_items, right_item) {
|
|
3054
|
+
const left = sanitize_status_text(left_items.join(" "));
|
|
3055
|
+
const right = right_item ? sanitize_status_text(right_item) : "";
|
|
3056
|
+
if (!left && !right) return void 0;
|
|
3057
|
+
if (!right) return truncateToWidth(theme.fg("dim", left), width, theme.fg("dim", "..."));
|
|
3058
|
+
if (!left) {
|
|
3059
|
+
const themed_right = theme.fg("dim", right);
|
|
3060
|
+
const right_width = visibleWidth(themed_right);
|
|
3061
|
+
return right_width >= width ? truncateToWidth(themed_right, width, theme.fg("dim", "...")) : `${" ".repeat(width - right_width)}${themed_right}`;
|
|
3062
|
+
}
|
|
3063
|
+
const right_width = visibleWidth(right);
|
|
3064
|
+
if (right_width >= width) return truncateToWidth(theme.fg("dim", right), width, theme.fg("dim", "..."));
|
|
3065
|
+
const min_gap = 1;
|
|
3066
|
+
const truncated_left = truncateToWidth(left, Math.max(0, width - right_width - min_gap), "...");
|
|
3067
|
+
const left_width = visibleWidth(truncated_left);
|
|
3068
|
+
const gap = Math.max(min_gap, width - left_width - right_width);
|
|
3069
|
+
return theme.fg("dim", truncated_left) + " ".repeat(gap) + theme.fg("dim", right);
|
|
3070
|
+
}
|
|
2611
3071
|
function get_current_thinking_level(ctx) {
|
|
2612
3072
|
const entries = ctx.sessionManager.getEntries();
|
|
2613
3073
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
@@ -2687,14 +3147,8 @@ function render_footer_lines(ctx, theme, footer_data, width, active_base_name, a
|
|
|
2687
3147
|
const dim_remainder = theme.fg("dim", remainder);
|
|
2688
3148
|
const lines = [truncateToWidth(theme.fg("dim", pwd), width, theme.fg("dim", "...")), dim_stats_left + dim_remainder];
|
|
2689
3149
|
const prompt_status = get_footer_prompt_status(active_base_name, active_layers);
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
const status_width = visibleWidth(themed_status);
|
|
2693
|
-
const aligned_status = status_width >= width ? truncateToWidth(themed_status, width, theme.fg("dim", "...")) : `${" ".repeat(width - status_width)}${themed_status}`;
|
|
2694
|
-
lines.push(aligned_status);
|
|
2695
|
-
}
|
|
2696
|
-
const other_statuses = Array.from(footer_data.getExtensionStatuses().entries()).filter(([key]) => key !== "preset").sort(([a], [b]) => a.localeCompare(b)).map(([, text]) => sanitize_status_text(text));
|
|
2697
|
-
if (other_statuses.length > 0) lines.push(truncateToWidth(other_statuses.join(" "), width, theme.fg("dim", "...")));
|
|
3150
|
+
const combined_status_line = render_footer_status_line(theme, width, Array.from(footer_data.getExtensionStatuses().entries()).filter(([key]) => key !== "preset").sort(([a], [b]) => a.localeCompare(b)).map(([, text]) => sanitize_status_text(text)), prompt_status);
|
|
3151
|
+
if (combined_status_line) lines.push(combined_status_line);
|
|
2698
3152
|
return lines;
|
|
2699
3153
|
}
|
|
2700
3154
|
function set_status(ctx, active_base_name, active_layers) {
|
|
@@ -3157,6 +3611,10 @@ async function prompt_presets(pi) {
|
|
|
3157
3611
|
//#endregion
|
|
3158
3612
|
//#region src/extensions/recall.ts
|
|
3159
3613
|
const DEFAULT_DB_PATH = join(process.env.HOME, ".pi", "pirecall.db");
|
|
3614
|
+
function should_inject_recall_prompt(event) {
|
|
3615
|
+
const selected_tools = event.systemPromptOptions?.selectedTools;
|
|
3616
|
+
return !selected_tools || selected_tools.includes("bash");
|
|
3617
|
+
}
|
|
3160
3618
|
function sync_recall_db_in_background() {
|
|
3161
3619
|
if (!existsSync(DEFAULT_DB_PATH)) return;
|
|
3162
3620
|
try {
|
|
@@ -3172,6 +3630,7 @@ async function recall(pi) {
|
|
|
3172
3630
|
sync_recall_db_in_background();
|
|
3173
3631
|
});
|
|
3174
3632
|
pi.on("before_agent_start", async (event) => {
|
|
3633
|
+
if (!should_inject_recall_prompt(event)) return {};
|
|
3175
3634
|
return { systemPrompt: event.systemPrompt + `
|
|
3176
3635
|
|
|
3177
3636
|
## Session Recall
|
|
@@ -3310,7 +3769,7 @@ async function session_name(pi) {
|
|
|
3310
3769
|
}
|
|
3311
3770
|
//#endregion
|
|
3312
3771
|
//#region src/skills/config.ts
|
|
3313
|
-
const DEFAULT_CONFIG$
|
|
3772
|
+
const DEFAULT_CONFIG$2 = {
|
|
3314
3773
|
version: 1,
|
|
3315
3774
|
enabled: {},
|
|
3316
3775
|
defaults: "all-disabled"
|
|
@@ -3320,7 +3779,7 @@ function get_config_path() {
|
|
|
3320
3779
|
}
|
|
3321
3780
|
function load_skills_config() {
|
|
3322
3781
|
const path = get_config_path();
|
|
3323
|
-
if (!existsSync(path)) return { ...DEFAULT_CONFIG$
|
|
3782
|
+
if (!existsSync(path)) return { ...DEFAULT_CONFIG$2 };
|
|
3324
3783
|
try {
|
|
3325
3784
|
const raw = readFileSync(path, "utf-8");
|
|
3326
3785
|
const parsed = JSON.parse(raw);
|
|
@@ -3330,7 +3789,7 @@ function load_skills_config() {
|
|
|
3330
3789
|
defaults: parsed.defaults ?? "all-enabled"
|
|
3331
3790
|
};
|
|
3332
3791
|
} catch {
|
|
3333
|
-
return { ...DEFAULT_CONFIG$
|
|
3792
|
+
return { ...DEFAULT_CONFIG$2 };
|
|
3334
3793
|
}
|
|
3335
3794
|
}
|
|
3336
3795
|
function save_skills_config(config) {
|
|
@@ -4057,7 +4516,7 @@ async function skills(pi) {
|
|
|
4057
4516
|
}
|
|
4058
4517
|
//#endregion
|
|
4059
4518
|
//#region src/extensions/telemetry-config.ts
|
|
4060
|
-
const DEFAULT_CONFIG = {
|
|
4519
|
+
const DEFAULT_CONFIG$1 = {
|
|
4061
4520
|
version: 1,
|
|
4062
4521
|
enabled: false
|
|
4063
4522
|
};
|
|
@@ -4073,15 +4532,15 @@ function resolve_telemetry_db_path(cwd, override_path) {
|
|
|
4073
4532
|
}
|
|
4074
4533
|
function load_telemetry_config() {
|
|
4075
4534
|
const path = get_telemetry_config_path();
|
|
4076
|
-
if (!existsSync(path)) return { ...DEFAULT_CONFIG };
|
|
4535
|
+
if (!existsSync(path)) return { ...DEFAULT_CONFIG$1 };
|
|
4077
4536
|
try {
|
|
4078
4537
|
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
4079
4538
|
return {
|
|
4080
|
-
version: typeof parsed.version === "number" ? parsed.version : DEFAULT_CONFIG.version,
|
|
4081
|
-
enabled: typeof parsed.enabled === "boolean" ? parsed.enabled : DEFAULT_CONFIG.enabled
|
|
4539
|
+
version: typeof parsed.version === "number" ? parsed.version : DEFAULT_CONFIG$1.version,
|
|
4540
|
+
enabled: typeof parsed.enabled === "boolean" ? parsed.enabled : DEFAULT_CONFIG$1.enabled
|
|
4082
4541
|
};
|
|
4083
4542
|
} catch {
|
|
4084
|
-
return { ...DEFAULT_CONFIG };
|
|
4543
|
+
return { ...DEFAULT_CONFIG$1 };
|
|
4085
4544
|
}
|
|
4086
4545
|
}
|
|
4087
4546
|
function save_telemetry_config(config) {
|
|
@@ -4240,6 +4699,10 @@ function infer_run_outcome(event) {
|
|
|
4240
4699
|
error_message: null
|
|
4241
4700
|
};
|
|
4242
4701
|
}
|
|
4702
|
+
function describe_session_shutdown(event) {
|
|
4703
|
+
const base = `session shutdown (${event.reason})`;
|
|
4704
|
+
return event.targetSessionFile ? `${base} → ${event.targetSessionFile}` : base;
|
|
4705
|
+
}
|
|
4243
4706
|
function format_telemetry_status(options) {
|
|
4244
4707
|
const override_label = options.override === void 0 ? "none" : options.override ? "--telemetry" : "--no-telemetry";
|
|
4245
4708
|
return [
|
|
@@ -4632,12 +5095,12 @@ function create_telemetry_extension(options = {}) {
|
|
|
4632
5095
|
headers_json: summarize_headers(event.headers)
|
|
4633
5096
|
});
|
|
4634
5097
|
});
|
|
4635
|
-
pi.on("session_shutdown", async () => {
|
|
5098
|
+
pi.on("session_shutdown", async (event) => {
|
|
4636
5099
|
if (store && active_run) store.finish_run({
|
|
4637
5100
|
id: active_run.id,
|
|
4638
5101
|
ended_at: now(),
|
|
4639
5102
|
success: null,
|
|
4640
|
-
error_message:
|
|
5103
|
+
error_message: describe_session_shutdown(event)
|
|
4641
5104
|
});
|
|
4642
5105
|
close_store();
|
|
4643
5106
|
active_run = null;
|
|
@@ -4648,6 +5111,137 @@ function create_telemetry_extension(options = {}) {
|
|
|
4648
5111
|
}
|
|
4649
5112
|
create_telemetry_extension();
|
|
4650
5113
|
//#endregion
|
|
5114
|
+
//#region src/extensions/working-indicator-config.ts
|
|
5115
|
+
const DEFAULT_CONFIG = {
|
|
5116
|
+
version: 1,
|
|
5117
|
+
mode: "spinner"
|
|
5118
|
+
};
|
|
5119
|
+
function get_working_indicator_config_path() {
|
|
5120
|
+
return join(getAgentDir(), "working-indicator.json");
|
|
5121
|
+
}
|
|
5122
|
+
function load_working_indicator_config() {
|
|
5123
|
+
const path = get_working_indicator_config_path();
|
|
5124
|
+
if (!existsSync(path)) return { ...DEFAULT_CONFIG };
|
|
5125
|
+
try {
|
|
5126
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
5127
|
+
return {
|
|
5128
|
+
version: typeof parsed.version === "number" ? parsed.version : DEFAULT_CONFIG.version,
|
|
5129
|
+
mode: is_working_indicator_mode(parsed.mode) ? parsed.mode : DEFAULT_CONFIG.mode
|
|
5130
|
+
};
|
|
5131
|
+
} catch {
|
|
5132
|
+
return { ...DEFAULT_CONFIG };
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5135
|
+
function save_working_indicator_config(config) {
|
|
5136
|
+
const path = get_working_indicator_config_path();
|
|
5137
|
+
const dir = dirname(path);
|
|
5138
|
+
if (!existsSync(dir)) mkdirSync(dir, {
|
|
5139
|
+
recursive: true,
|
|
5140
|
+
mode: 448
|
|
5141
|
+
});
|
|
5142
|
+
const tmp = `${path}.tmp-${Date.now()}`;
|
|
5143
|
+
writeFileSync(tmp, JSON.stringify(config, null, " ") + "\n", { mode: 384 });
|
|
5144
|
+
renameSync(tmp, path);
|
|
5145
|
+
}
|
|
5146
|
+
function is_working_indicator_mode(value) {
|
|
5147
|
+
return value === "default" || value === "dot" || value === "none" || value === "pulse" || value === "spinner";
|
|
5148
|
+
}
|
|
5149
|
+
//#endregion
|
|
5150
|
+
//#region src/extensions/working-indicator.ts
|
|
5151
|
+
const COMMAND_MODES = [
|
|
5152
|
+
"dot",
|
|
5153
|
+
"none",
|
|
5154
|
+
"pulse",
|
|
5155
|
+
"spinner",
|
|
5156
|
+
"reset"
|
|
5157
|
+
];
|
|
5158
|
+
const SPINNER_FRAMES = [
|
|
5159
|
+
"⠋",
|
|
5160
|
+
"⠙",
|
|
5161
|
+
"⠹",
|
|
5162
|
+
"⠸",
|
|
5163
|
+
"⠼",
|
|
5164
|
+
"⠴",
|
|
5165
|
+
"⠦",
|
|
5166
|
+
"⠧",
|
|
5167
|
+
"⠇",
|
|
5168
|
+
"⠏"
|
|
5169
|
+
];
|
|
5170
|
+
function get_working_indicator(ctx, mode) {
|
|
5171
|
+
switch (mode) {
|
|
5172
|
+
case "dot": return { frames: [ctx.ui.theme.fg("accent", "●")] };
|
|
5173
|
+
case "none": return { frames: [] };
|
|
5174
|
+
case "pulse": return {
|
|
5175
|
+
frames: [
|
|
5176
|
+
ctx.ui.theme.fg("dim", "·"),
|
|
5177
|
+
ctx.ui.theme.fg("muted", "•"),
|
|
5178
|
+
ctx.ui.theme.fg("accent", "●"),
|
|
5179
|
+
ctx.ui.theme.fg("muted", "•")
|
|
5180
|
+
],
|
|
5181
|
+
intervalMs: 120
|
|
5182
|
+
};
|
|
5183
|
+
case "spinner": return {
|
|
5184
|
+
frames: SPINNER_FRAMES.map((frame, index) => ctx.ui.theme.fg(index % 2 === 0 ? "accent" : "muted", frame)),
|
|
5185
|
+
intervalMs: 80
|
|
5186
|
+
};
|
|
5187
|
+
case "default": return;
|
|
5188
|
+
}
|
|
5189
|
+
}
|
|
5190
|
+
function describe_working_indicator_mode(mode) {
|
|
5191
|
+
switch (mode) {
|
|
5192
|
+
case "dot": return "static dot";
|
|
5193
|
+
case "none": return "hidden";
|
|
5194
|
+
case "pulse": return "pulse";
|
|
5195
|
+
case "spinner": return "custom spinner";
|
|
5196
|
+
case "default": return "pi default spinner";
|
|
5197
|
+
}
|
|
5198
|
+
}
|
|
5199
|
+
function parse_working_indicator_mode(input) {
|
|
5200
|
+
const normalized = input.trim().toLowerCase();
|
|
5201
|
+
if (!normalized) return null;
|
|
5202
|
+
if (normalized === "reset" || normalized === "default") return "default";
|
|
5203
|
+
if (normalized === "dot" || normalized === "none" || normalized === "pulse" || normalized === "spinner") return normalized;
|
|
5204
|
+
return null;
|
|
5205
|
+
}
|
|
5206
|
+
function apply_working_indicator(ctx, mode) {
|
|
5207
|
+
ctx.ui.setWorkingIndicator(get_working_indicator(ctx, mode));
|
|
5208
|
+
}
|
|
5209
|
+
async function working_indicator(pi) {
|
|
5210
|
+
let mode = load_working_indicator_config().mode;
|
|
5211
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
5212
|
+
mode = load_working_indicator_config().mode;
|
|
5213
|
+
apply_working_indicator(ctx, mode);
|
|
5214
|
+
});
|
|
5215
|
+
pi.registerCommand("working-indicator", {
|
|
5216
|
+
description: "Set the streaming working indicator: dot, pulse, none, spinner, or reset",
|
|
5217
|
+
getArgumentCompletions: (prefix) => {
|
|
5218
|
+
const value = prefix.trim().toLowerCase();
|
|
5219
|
+
return COMMAND_MODES.filter((entry) => entry.startsWith(value)).map((entry) => ({
|
|
5220
|
+
value: entry,
|
|
5221
|
+
label: entry
|
|
5222
|
+
}));
|
|
5223
|
+
},
|
|
5224
|
+
handler: async (args, ctx) => {
|
|
5225
|
+
const next = parse_working_indicator_mode(args);
|
|
5226
|
+
if (next === null) {
|
|
5227
|
+
if (!args.trim()) {
|
|
5228
|
+
ctx.ui.notify(`Working indicator: ${describe_working_indicator_mode(mode)}`, "info");
|
|
5229
|
+
return;
|
|
5230
|
+
}
|
|
5231
|
+
ctx.ui.notify("Usage: /working-indicator [dot|pulse|none|spinner|reset]", "error");
|
|
5232
|
+
return;
|
|
5233
|
+
}
|
|
5234
|
+
mode = next;
|
|
5235
|
+
save_working_indicator_config({
|
|
5236
|
+
version: 1,
|
|
5237
|
+
mode
|
|
5238
|
+
});
|
|
5239
|
+
apply_working_indicator(ctx, mode);
|
|
5240
|
+
ctx.ui.notify(`Working indicator set to: ${describe_working_indicator_mode(mode)}`, "info");
|
|
5241
|
+
}
|
|
5242
|
+
});
|
|
5243
|
+
}
|
|
5244
|
+
//#endregion
|
|
4651
5245
|
//#region src/api.ts
|
|
4652
5246
|
const BUILTIN_EXTENSION_FACTORIES = {
|
|
4653
5247
|
mcp,
|
|
@@ -4658,7 +5252,10 @@ const BUILTIN_EXTENSION_FACTORIES = {
|
|
|
4658
5252
|
recall,
|
|
4659
5253
|
"prompt-presets": prompt_presets,
|
|
4660
5254
|
lsp: lsp_default,
|
|
4661
|
-
"session-name": session_name
|
|
5255
|
+
"session-name": session_name,
|
|
5256
|
+
"confirm-destructive": confirm_destructive,
|
|
5257
|
+
"hooks-resolution": hooks_resolution_default,
|
|
5258
|
+
"working-indicator": working_indicator
|
|
4662
5259
|
};
|
|
4663
5260
|
const PACKAGE_THEME_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..", "themes");
|
|
4664
5261
|
const PI_AGENT_DIR_ENV = "PI_CODING_AGENT_DIR";
|
|
@@ -4676,6 +5273,9 @@ function get_force_disabled_builtins(options) {
|
|
|
4676
5273
|
if (!options.prompt_presets) force_disabled.add("prompt-presets");
|
|
4677
5274
|
if (!options.lsp) force_disabled.add("lsp");
|
|
4678
5275
|
if (!options.session_name) force_disabled.add("session-name");
|
|
5276
|
+
if (!options.confirm_destructive) force_disabled.add("confirm-destructive");
|
|
5277
|
+
if (!options.hooks_resolution) force_disabled.add("hooks-resolution");
|
|
5278
|
+
if (!options.working_indicator) force_disabled.add("working-indicator");
|
|
4679
5279
|
return force_disabled;
|
|
4680
5280
|
}
|
|
4681
5281
|
function create_builtin_extension_factory(key, extension, force_disabled) {
|
|
@@ -4697,7 +5297,7 @@ function create_extensions_override(managed_inline_paths) {
|
|
|
4697
5297
|
};
|
|
4698
5298
|
}
|
|
4699
5299
|
async function create_my_pi(options = {}) {
|
|
4700
|
-
const { cwd = process.cwd(), agent_dir, extensions = [], extensionFactories: user_factories = [], mcp = true, skills = true, chain = true, filter_output = true, handoff = true, recall = true, prompt_presets = true, lsp = true, session_name = true, telemetry, telemetry_db_path, model, system_prompt, append_system_prompt } = options;
|
|
5300
|
+
const { cwd = process.cwd(), agent_dir, extensions = [], extensionFactories: user_factories = [], mcp = true, skills = true, chain = true, filter_output = true, handoff = true, recall = true, prompt_presets = true, lsp = true, session_name = true, confirm_destructive = true, hooks_resolution = true, working_indicator = true, telemetry, telemetry_db_path, model, system_prompt, append_system_prompt } = options;
|
|
4701
5301
|
const effective_agent_dir = resolve_agent_dir(cwd, agent_dir);
|
|
4702
5302
|
if (agent_dir) process.env[PI_AGENT_DIR_ENV] = effective_agent_dir;
|
|
4703
5303
|
const resolved_extensions = extensions.map((p) => resolve(cwd, p));
|
|
@@ -4710,7 +5310,10 @@ async function create_my_pi(options = {}) {
|
|
|
4710
5310
|
recall,
|
|
4711
5311
|
prompt_presets,
|
|
4712
5312
|
lsp,
|
|
4713
|
-
session_name
|
|
5313
|
+
session_name,
|
|
5314
|
+
confirm_destructive,
|
|
5315
|
+
hooks_resolution,
|
|
5316
|
+
working_indicator
|
|
4714
5317
|
});
|
|
4715
5318
|
const managed_extension_factories = [
|
|
4716
5319
|
create_telemetry_extension({
|
|
@@ -4768,4 +5371,4 @@ async function create_my_pi(options = {}) {
|
|
|
4768
5371
|
//#endregion
|
|
4769
5372
|
export { create_my_pi as n, runPrintMode$1 as r, InteractiveMode$1 as t };
|
|
4770
5373
|
|
|
4771
|
-
//# sourceMappingURL=api-
|
|
5374
|
+
//# sourceMappingURL=api-BVS4nqfD.js.map
|