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.
@@ -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$2 = {
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$2 };
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$2 };
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 && ctx.hasUI) {
581
- const states = resolve_builtin_extension_states(force_disabled);
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
- const configs = load_mcp_config(process.cwd());
2181
- const servers = new Map(configs.map((config) => [config.name, {
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 connect_server = async (state) => {
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
- connect_all_servers();
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
- await connect_all_servers();
2252
- return event;
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
- connect_server(server);
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
- if (prompt_status) {
2691
- const themed_status = theme.fg("dim", prompt_status);
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$1 = {
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$1 };
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$1 };
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: "session shutdown"
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-1ZXLxSgP.js.map
5374
+ //# sourceMappingURL=api-BVS4nqfD.js.map