/**
 * Telegram extension registration helpers
 * Owns tool, command, and lifecycle-hook registration so index.ts can stay focused on runtime orchestration state and side effects
 */

import { Type } from "@sinclair/typebox";

import {
  queueTelegramAttachments,
  TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES,
  type TelegramAttachmentQueueTargetView,
} from "./attachments.ts";
import type {
  AgentEndEvent,
  AgentStartEvent,
  BeforeAgentStartEvent,
  ExtensionAPI,
  ExtensionCommandContext,
  ExtensionContext,
  SessionShutdownEvent,
  SessionStartEvent,
} from "./pi.ts";
import { TELEGRAM_PREFIX } from "./turns.ts";

const MAX_ATTACHMENTS_PER_TURN = 10;

const SYSTEM_PROMPT_SUFFIX = `

Telegram bridge extension is active.
- Messages forwarded from Telegram are prefixed with "[telegram]".
- [telegram] messages may include [attachments] sections with a base directory plus relative local file entries. Resolve and read those files as needed.
- Telegram is often read on narrow phone screens, so prefer narrow table columns when presenting tabular data; wide monospace tables can become unreadable.
- If a [telegram] user asked for a file or generated artifact, use the telegram_attach tool with the local file path so the extension can send it with your next final reply.
- Do not assume mentioning a local file path in plain text will send it to Telegram. Use telegram_attach.`;

// --- Tool Registration ---

export interface TelegramRuntimeEventRecorderPort {
  recordRuntimeEvent?: (
    category: string,
    error: unknown,
    details?: Record<string, unknown>,
  ) => void;
}

export interface TelegramAttachmentToolRegistrationDeps
  extends TelegramRuntimeEventRecorderPort {
  maxAttachmentsPerTurn?: number;
  maxAttachmentSizeBytes?: number;
  getActiveTurn: () => TelegramAttachmentQueueTargetView | undefined;
  statPath?: (path: string) => Promise<{ isFile(): boolean; size?: number }>;
}

export function registerTelegramAttachmentTool(
  pi: ExtensionAPI,
  deps: TelegramAttachmentToolRegistrationDeps,
): void {
  const maxAttachmentsPerTurn =
    deps.maxAttachmentsPerTurn ?? MAX_ATTACHMENTS_PER_TURN;
  const maxAttachmentSizeBytes =
    deps.maxAttachmentSizeBytes ?? TELEGRAM_OUTBOUND_ATTACHMENT_MAX_BYTES;
  pi.registerTool({
    name: "telegram_attach",
    label: "Telegram Attach",
    description:
      "Queue one or more local files to be sent with the next Telegram reply.",
    promptSnippet: "Queue local files to be sent with the next Telegram reply.",
    promptGuidelines: [
      "When handling a [telegram] message and the user asked for a file or generated artifact, call telegram_attach with the local path instead of only mentioning the path in text.",
    ],
    parameters: Type.Object({
      paths: Type.Array(
        Type.String({ description: "Local file path to attach" }),
        { minItems: 1, maxItems: maxAttachmentsPerTurn },
      ),
    }),
    async execute(_toolCallId, params) {
      try {
        return await queueTelegramAttachments({
          activeTurn: deps.getActiveTurn(),
          paths: params.paths,
          maxAttachmentsPerTurn,
          maxAttachmentSizeBytes,
          statPath: deps.statPath,
        });
      } catch (error) {
        deps.recordRuntimeEvent?.("attachment", error, {
          phase: "queue",
          count: params.paths.length,
        });
        throw error;
      }
    },
  });
}

// --- Command Registration ---

export interface TelegramCommandStartPollingOptions {
  force?: boolean;
}

export interface TelegramCommandStartPollingResult {
  ok: boolean;
  message?: string;
  canTakeover?: boolean;
  owner?: string;
}

export interface TelegramCommandRegistrationDeps {
  promptForConfig: (ctx: ExtensionCommandContext) => Promise<void>;
  getStatusLines: () => string[];
  reloadConfig: () => Promise<void>;
  hasBotToken: () => boolean;
  startPolling: (
    ctx: ExtensionCommandContext,
    options?: TelegramCommandStartPollingOptions,
  ) =>
    | void
    | Promise<void | TelegramCommandStartPollingResult>
    | TelegramCommandStartPollingResult;
  stopPolling: () => Promise<void | string>;
  updateStatus: (ctx: ExtensionCommandContext) => void;
}

function formatTelegramTakeoverTitle(ctx: ExtensionCommandContext): string {
  return ctx.ui.theme.fg("accent", "pi-telegram");
}

function formatTelegramTakeoverPrompt(
  ctx: ExtensionCommandContext,
  owner?: string,
): string {
  const theme = ctx.ui.theme;
  const action = theme.fg("warning", "move singleton lock here?");
  const from = theme.fg("muted", "from:");
  const to = theme.fg("muted", "to:");
  const source = owner ?? "another pi instance";
  return `${action}\n\n${from} ${source}\n${to} ${ctx.cwd}`;
}

export function registerTelegramCommands(
  pi: ExtensionAPI,
  deps: TelegramCommandRegistrationDeps,
): void {
  pi.registerCommand("telegram-setup", {
    description: "Configure Telegram bot token",
    handler: async (_args, ctx) => {
      await deps.promptForConfig(ctx);
    },
  });
  pi.registerCommand("telegram-status", {
    description: "Show Telegram bridge status",
    handler: async (_args, ctx) => {
      ctx.ui.notify(deps.getStatusLines().join("\n"), "info");
    },
  });
  pi.registerCommand("telegram-connect", {
    description: "Start the Telegram bridge in this pi session",
    handler: async (_args, ctx) => {
      await deps.reloadConfig();
      if (!deps.hasBotToken()) {
        await deps.promptForConfig(ctx);
        return;
      }
      let result = await deps.startPolling(ctx);
      if (result && !result.ok && result.canTakeover) {
        const confirmed = await ctx.ui.confirm(
          formatTelegramTakeoverTitle(ctx),
          formatTelegramTakeoverPrompt(ctx, result.owner),
        );
        if (!confirmed) {
          ctx.ui.notify("Telegram bridge takeover cancelled.", "info");
          deps.updateStatus(ctx);
          return;
        }
        result = await deps.startPolling(ctx, { force: true });
      }
      if (result?.message) {
        ctx.ui.notify(result.message, result.ok ? "info" : "warning");
      }
      deps.updateStatus(ctx);
    },
  });
  pi.registerCommand("telegram-disconnect", {
    description: "Stop the Telegram bridge in this pi session",
    handler: async (_args, ctx) => {
      const message = await deps.stopPolling();
      if (message) ctx.ui.notify(message, "info");
      deps.updateStatus(ctx);
    },
  });
}

// --- Lifecycle Hook Registration ---

export function buildTelegramBridgeSystemPrompt(options: {
  prompt: string;
  systemPrompt: string;
  telegramPrefix?: string;
  systemPromptSuffix: string;
}): { systemPrompt: string } {
  const telegramPrefix = options.telegramPrefix ?? TELEGRAM_PREFIX;
  const suffix = options.prompt.trimStart().startsWith(telegramPrefix)
    ? `${options.systemPromptSuffix}\n- The current user message came from Telegram.`
    : options.systemPromptSuffix;
  return { systemPrompt: options.systemPrompt + suffix };
}

export function createTelegramBeforeAgentStartHook(
  options: {
    telegramPrefix?: string;
    systemPromptSuffix?: string;
  } = {},
): (event: BeforeAgentStartEvent) => { systemPrompt: string } {
  return (event) =>
    buildTelegramBridgeSystemPrompt({
      prompt: event.prompt,
      systemPrompt: event.systemPrompt,
      telegramPrefix: options.telegramPrefix,
      systemPromptSuffix: options.systemPromptSuffix ?? SYSTEM_PROMPT_SUFFIX,
    });
}

export interface TelegramBeforeAgentStartResult {
  systemPrompt?: string;
}

type TelegramBeforeAgentStartReturn =
  | Promise<TelegramBeforeAgentStartResult | undefined>
  | TelegramBeforeAgentStartResult
  | undefined;

type TelegramLifecycleModel = ExtensionContext["model"];
type TelegramLifecycleMessage = AgentEndEvent["messages"][number];

export interface TelegramLifecycleRegistrationDeps {
  onSessionStart: (
    event: SessionStartEvent,
    ctx: ExtensionContext,
  ) => Promise<void>;
  onSessionShutdown: (
    event: SessionShutdownEvent,
    ctx: ExtensionContext,
  ) => Promise<void>;
  onBeforeAgentStart: (
    event: BeforeAgentStartEvent,
    ctx: ExtensionContext,
  ) => TelegramBeforeAgentStartReturn;
  onModelSelect: (
    event: { model: TelegramLifecycleModel },
    ctx: ExtensionContext,
  ) => Promise<void> | void;
  onAgentStart: (
    event: AgentStartEvent,
    ctx: ExtensionContext,
  ) => Promise<void>;
  onToolExecutionStart: (
    event: unknown,
    ctx: ExtensionContext,
  ) => Promise<void> | void;
  onToolExecutionEnd: (
    event: unknown,
    ctx: ExtensionContext,
  ) => Promise<void> | void;
  onMessageStart: (
    event: { message: TelegramLifecycleMessage },
    ctx: ExtensionContext,
  ) => Promise<void>;
  onMessageUpdate: (
    event: { message: TelegramLifecycleMessage },
    ctx: ExtensionContext,
  ) => Promise<void>;
  onAgentEnd: (event: AgentEndEvent, ctx: ExtensionContext) => Promise<void>;
}

export interface TelegramSessionLifecycleHooks {
  onSessionStart: (event: SessionStartEvent, ctx: ExtensionContext) => Promise<void>;
  onSessionShutdown: (
    event: SessionShutdownEvent,
    ctx: ExtensionContext,
  ) => Promise<void>;
}

export interface TelegramExtraLifecycleHooks {
  onSessionStart?: (
    event: SessionStartEvent,
    ctx: ExtensionContext,
  ) => Promise<void>;
  onSessionShutdown?: (
    event: SessionShutdownEvent,
    ctx: ExtensionContext,
  ) => Promise<void>;
}

export function appendTelegramLifecycleHooks(
  base: TelegramSessionLifecycleHooks,
  extra: TelegramExtraLifecycleHooks,
): TelegramSessionLifecycleHooks {
  return {
    onSessionStart: async (event, ctx) => {
      await base.onSessionStart(event, ctx);
      await extra.onSessionStart?.(event, ctx);
    },
    onSessionShutdown: async (event, ctx) => {
      await base.onSessionShutdown(event, ctx);
      await extra.onSessionShutdown?.(event, ctx);
    },
  };
}

export function registerTelegramLifecycleHooks(
  pi: ExtensionAPI,
  deps: TelegramLifecycleRegistrationDeps,
): void {
  pi.on("session_start", async (event, ctx) => {
    await deps.onSessionStart(event, ctx);
  });
  pi.on("session_shutdown", async (event, ctx) => {
    await deps.onSessionShutdown(event, ctx);
  });
  pi.on("before_agent_start", async (event, ctx) => {
    return deps.onBeforeAgentStart(event, ctx);
  });
  pi.on("model_select", async (event, ctx) => {
    await deps.onModelSelect(event, ctx);
  });
  pi.on("agent_start", async (event, ctx) => {
    await deps.onAgentStart(event, ctx);
  });
  pi.on("tool_execution_start", async (event, ctx) => {
    await deps.onToolExecutionStart(event, ctx);
  });
  pi.on("tool_execution_end", async (event, ctx) => {
    await deps.onToolExecutionEnd(event, ctx);
  });
  pi.on("message_start", async (event, ctx) => {
    await deps.onMessageStart(event, ctx);
  });
  pi.on("message_update", async (event, ctx) => {
    await deps.onMessageUpdate(event, ctx);
  });
  pi.on("agent_end", async (event, ctx) => {
    await deps.onAgentEnd(event, ctx);
  });
}


/**
 * Telegram singleton lock helpers
 * Owns shared locks.json access and Telegram bridge ownership semantics
 */

import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { dirname, join, resolve } from "node:path";

export const TELEGRAM_LOCK_KEY = "@llblab/pi-telegram";

function getAgentDir(): string {
  return process.env.PI_CODING_AGENT_DIR
    ? resolve(process.env.PI_CODING_AGENT_DIR)
    : join(homedir(), ".pi", "agent");
}

function getLocksPath(): string {
  return join(getAgentDir(), "locks.json");
}

export interface TelegramLockEntry {
  pid: number;
  cwd?: string;
}

export interface TelegramLockContext {
  cwd: string;
}

export type TelegramLockState =
  | { kind: "inactive" }
  | { kind: "active-here"; lock: TelegramLockEntry }
  | { kind: "active-elsewhere"; lock: TelegramLockEntry }
  | { kind: "stale"; lock: TelegramLockEntry };

export interface TelegramLockAcquireOptions {
  force?: boolean;
}

export type TelegramLockAcquireResult =
  | { ok: true; lock: TelegramLockEntry; replacedStale: boolean }
  | { ok: false; lock: TelegramLockEntry };

export interface TelegramLockRuntime<TContext extends TelegramLockContext> {
  acquire: (
    ctx: TContext,
    options?: TelegramLockAcquireOptions,
  ) => TelegramLockAcquireResult;
  release: () => TelegramLockState;
  getState: () => TelegramLockState;
  getStatusLabel: () => string;
  owns: (ctx?: TelegramLockContext) => boolean;
}

export interface TelegramLockRuntimeOptions {
  key?: string;
  locksPath?: string;
  pid?: number;
  isProcessAlive?: (pid: number) => boolean;
}

export interface TelegramLockedPollingStartOptions {
  force?: boolean;
}

export type TelegramLockedPollingStartResult =
  | { ok: true; message: string; canTakeover?: false }
  | { ok: false; message: string; canTakeover?: boolean; owner?: string };

export interface TelegramLockedPollingRuntime<TContext extends TelegramLockContext> {
  start: (
    ctx: TContext,
    options?: TelegramLockedPollingStartOptions,
  ) => Promise<TelegramLockedPollingStartResult>;
  stop: () => Promise<string>;
  suspend: () => Promise<void>;
  onSessionStart: (_event: unknown, ctx: TContext) => Promise<void>;
}

export interface TelegramLockedPollingRuntimeDeps<TContext extends TelegramLockContext> {
  lock: TelegramLockRuntime<TContext>;
  hasBotToken: () => boolean;
  startPolling: (ctx: TContext) => void | Promise<void>;
  stopPolling: () => Promise<void>;
  updateStatus: (ctx: TContext) => void;
  recordRuntimeEvent?: (category: string, error: unknown, details?: Record<string, unknown>) => void;
  ownershipCheckMs?: number;
}

export function readLocks(path = getLocksPath()): Record<string, unknown> {
  if (!existsSync(path)) return {};
  try {
    const value = JSON.parse(readFileSync(path, "utf8"));
    return value && typeof value === "object" && !Array.isArray(value)
      ? (value as Record<string, unknown>)
      : {};
  } catch {
    return {};
  }
}

export function writeLocks(path: string, locks: Record<string, unknown>): void {
  mkdirSync(dirname(path), { recursive: true });
  const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
  try {
    writeFileSync(tempPath, `${JSON.stringify(locks, null, 2)}\n`, "utf8");
    renameSync(tempPath, path);
  } catch (error) {
    try {
      unlinkSync(tempPath);
    } catch {
      /* best effort */
    }
    throw error;
  }
}

export function parseTelegramLockEntry(value: unknown): TelegramLockEntry | undefined {
  if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
  const record = value as Record<string, unknown>;
  if (typeof record.pid !== "number") return undefined;
  return {
    pid: record.pid,
    cwd: typeof record.cwd === "string" ? record.cwd : undefined,
  };
}

export function isProcessAlive(pid: number): boolean {
  if (!Number.isInteger(pid) || pid <= 0) return false;
  try {
    process.kill(pid, 0);
    return true;
  } catch (error) {
    return (error as { code?: string }).code === "EPERM";
  }
}

function formatLock(lock: TelegramLockEntry): string {
  return lock.cwd ? `pid ${lock.pid}, cwd ${lock.cwd}` : `pid ${lock.pid}`;
}

function getLockState(lock: TelegramLockEntry | undefined, pid: number, isAlive: (pid: number) => boolean): TelegramLockState {
  if (!lock) return { kind: "inactive" };
  if (lock.pid === pid) return { kind: "active-here", lock };
  if (isAlive(lock.pid)) return { kind: "active-elsewhere", lock };
  return { kind: "stale", lock };
}

function ownsLockContext(
  lock: TelegramLockEntry | undefined,
  pid: number,
  ctx?: TelegramLockContext,
): boolean {
  if (!lock || lock.pid !== pid) return false;
  return !lock.cwd || !ctx || lock.cwd === ctx.cwd;
}

function snapshotLockContext(ctx: TelegramLockContext): TelegramLockContext {
  return { cwd: ctx.cwd };
}

function formatLockState(state: TelegramLockState): string {
  switch (state.kind) {
    case "inactive":
      return "inactive";
    case "active-here":
      return "active here";
    case "active-elsewhere":
      return `active elsewhere (${formatLock(state.lock)})`;
    case "stale":
      return `stale (${formatLock(state.lock)})`;
  }
}

export function createTelegramLockRuntime<TContext extends TelegramLockContext>(
  options: TelegramLockRuntimeOptions = {},
): TelegramLockRuntime<TContext> {
  const key = options.key ?? TELEGRAM_LOCK_KEY;
  const locksPath = options.locksPath ?? getLocksPath();
  const pid = options.pid ?? process.pid;
  const isAlive = options.isProcessAlive ?? isProcessAlive;
  const readLock = () => parseTelegramLockEntry(readLocks(locksPath)[key]);
  const writeLock = (lock: TelegramLockEntry) => {
    const locks = readLocks(locksPath);
    locks[key] = lock;
    writeLocks(locksPath, locks);
  };
  return {
    acquire: (ctx, acquireOptions = {}) => {
      const state = getLockState(readLock(), pid, isAlive);
      if (state.kind === "active-elsewhere" && !acquireOptions.force)
        return { ok: false, lock: state.lock };
      const lock = { pid, cwd: ctx.cwd };
      writeLock(lock);
      return { ok: true, lock, replacedStale: state.kind === "stale" };
    },
    release: () => {
      const state = getLockState(readLock(), pid, isAlive);
      if (state.kind === "active-here" || state.kind === "stale") {
        const locks = readLocks(locksPath);
        delete locks[key];
        writeLocks(locksPath, locks);
      }
      return state;
    },
    getState: () => getLockState(readLock(), pid, isAlive),
    getStatusLabel: () => formatLockState(getLockState(readLock(), pid, isAlive)),
    owns: (ctx) => ownsLockContext(readLock(), pid, ctx),
  };
}

export function createTelegramLockedPollingRuntime<TContext extends TelegramLockContext>(
  deps: TelegramLockedPollingRuntimeDeps<TContext>,
): TelegramLockedPollingRuntime<TContext> {
  let ownershipInterval: ReturnType<typeof setInterval> | undefined;
  let ownershipStop: Promise<void> | undefined;
  const ownershipCheckMs = deps.ownershipCheckMs ?? 1000;
  const stopOwnershipWatcher = () => {
    if (!ownershipInterval) return;
    clearInterval(ownershipInterval);
    ownershipInterval = undefined;
  };
  const updateStatusSafely = (ctx: TContext, phase: string) => {
    try {
      deps.updateStatus(ctx);
    } catch (error) {
      deps.recordRuntimeEvent?.("lock", error, { phase });
    }
  };
  const suspendPolling = async () => {
    stopOwnershipWatcher();
    if (ownershipStop) {
      await ownershipStop;
      return;
    }
    await deps.stopPolling();
  };
  const stopAfterOwnershipLoss = (ctx: TContext) => {
    if (ownershipStop) return;
    stopOwnershipWatcher();
    ownershipStop = deps.stopPolling()
      .catch((error) => deps.recordRuntimeEvent?.("lock", error, { phase: "ownership-loss" }))
      .finally(() => {
        ownershipStop = undefined;
        updateStatusSafely(ctx, "ownership-loss-status");
      });
  };
  const startOwnershipWatcher = (ctx: TContext) => {
    const owner = snapshotLockContext(ctx);
    stopOwnershipWatcher();
    ownershipInterval = setInterval(() => {
      if (deps.lock.owns(owner)) return;
      stopAfterOwnershipLoss(ctx);
    }, ownershipCheckMs);
    ownershipInterval.unref?.();
  };
  return {
    start: async (ctx, options = {}) => {
      if (!deps.hasBotToken()) return { ok: false, message: "Telegram bot is not configured." };
      const acquired = deps.lock.acquire(ctx, options);
      if (!acquired.ok) {
        return {
          ok: false,
          canTakeover: true,
          owner: formatLock(acquired.lock),
          message: `Telegram bridge is active in another pi instance (${formatLock(acquired.lock)}).`,
        };
      }
      await deps.startPolling(ctx);
      startOwnershipWatcher(ctx);
      deps.updateStatus(ctx);
      const staleSuffix = acquired.replacedStale ? " Replaced stale lock." : "";
      return { ok: true, message: `Telegram bridge connected.${staleSuffix}` };
    },
    stop: async () => {
      await suspendPolling();
      const state = deps.lock.release();
      if (state.kind === "active-elsewhere") {
        return `Telegram bridge is active in another pi instance (${formatLock(state.lock)}).`;
      }
      if (state.kind === "stale") return `Removed stale Telegram bridge lock (${formatLock(state.lock)}).`;
      return "Telegram bridge disconnected.";
    },
    suspend: suspendPolling,
    onSessionStart: async (_event, ctx) => {
      if (!deps.hasBotToken()) return;
      const ownsCurrentLock = deps.lock.owns(ctx);
      const state = ownsCurrentLock ? undefined : deps.lock.getState();
      const canResumeStaleSameCwd =
        state?.kind === "stale" && state.lock.cwd === ctx.cwd;
      if (!ownsCurrentLock && !canResumeStaleSameCwd) return;
      try {
        if (canResumeStaleSameCwd) {
          const acquired = deps.lock.acquire(ctx);
          if (!acquired.ok) return;
        }
        await deps.startPolling(ctx);
        startOwnershipWatcher(ctx);
        deps.updateStatus(ctx);
      } catch (error) {
        deps.recordRuntimeEvent?.("lock", error, { phase: "auto-start" });
      }
    },
  };
}


**Checking connection and configuration**

I need to determine if I'm successfully connected. It seems like I might have to lock points with the old pid 10916, but that could be stale. I’ll check the process status for that pid. Also, I want to inspect the Telegram status because there could be a configuration path mismatch with PI_CODING_AGENT_DIR causing the config in pi-config not to align with /.pi/agent. Time to read through config.ts and setup!

bash

ps -p 10916 -o pid=,cmd= || true && echo '--- locks file ---' && cat /home/sebas/pi-config/locks.json && echo '--- telegram config locations ---' && ls -l /home/sebas/pi-config/telegram.json /home/sebas/.pi/agent/telegram.json 2>/dev/null || true


  10916 pi
--- locks file ---
{
  "@llblab/pi-telegram": {
    "pid": 10916,
    "cwd": "/home/sebas"
  }
}
--- telegram config locations ---
-rw------- 1 sebas sebas 179 May  1 17:23 /home/sebas/pi-config/telegram.json


/**
 * Telegram bridge config and pairing helpers
 * Owns persisted bot/session pairing state, local config storage, authorization policy, and first-user pairing side effects
 */

import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
import { homedir } from "node:os";
import { join, resolve } from "node:path";

import type { TelegramAttachmentHandlerConfig } from "./handlers.ts";

function getAgentDir(): string {
  return process.env.PI_CODING_AGENT_DIR
    ? resolve(process.env.PI_CODING_AGENT_DIR)
    : join(homedir(), ".pi", "agent");
}

function getConfigPath(): string {
  return join(getAgentDir(), "telegram.json");
}

export interface TelegramConfig {
  botToken?: string;
  botUsername?: string;
  botId?: number;
  allowedUserId?: number;
  lastUpdateId?: number;
  attachmentHandlers?: TelegramAttachmentHandlerConfig[];
}

export interface TelegramConfigStore {
  get: () => TelegramConfig;
  set: (config: TelegramConfig) => void;
  update: (mutate: (config: TelegramConfig) => void) => void;
  getBotToken: () => string | undefined;
  hasBotToken: () => boolean;
  getAllowedUserId: () => number | undefined;
  getAttachmentHandlers: () => TelegramAttachmentHandlerConfig[] | undefined;
  setAllowedUserId: (userId: number) => void;
  load: () => Promise<void>;
  persist: (config?: TelegramConfig) => Promise<void>;
}

export interface TelegramConfigStoreOptions {
  initialConfig?: TelegramConfig;
  agentDir?: string;
  configPath?: string;
}

export async function readTelegramConfig(
  configPath: string,
): Promise<TelegramConfig> {
  try {
    const content = await readFile(configPath, "utf8");
    return JSON.parse(content) as TelegramConfig;
  } catch {
    return {};
  }
}

export async function writeTelegramConfig(
  agentDir: string,
  configPath: string,
  config: TelegramConfig,
): Promise<void> {
  await mkdir(agentDir, { recursive: true });
  await writeFile(configPath, JSON.stringify(config, null, "\t") + "\n", {
    encoding: "utf8",
    mode: 0o600,
  });
  await chmod(configPath, 0o600);
}

export function createTelegramConfigStore(
  options: TelegramConfigStoreOptions = {},
): TelegramConfigStore {
  let config: TelegramConfig = options.initialConfig ?? {};
  const agentDir = options.agentDir ?? getAgentDir();
  const configPath = options.configPath ?? getConfigPath();
  return {
    get: () => config,
    set: (nextConfig) => {
      config = nextConfig;
    },
    update: (mutate) => {
      mutate(config);
    },
    getBotToken: () => config.botToken,
    hasBotToken: () => !!config.botToken,
    getAllowedUserId: () => config.allowedUserId,
    getAttachmentHandlers: () => config.attachmentHandlers,
    setAllowedUserId: (userId) => {
      config.allowedUserId = userId;
    },
    load: async () => {
      config = await readTelegramConfig(configPath);
    },
    persist: async (nextConfig = config) => {
      await writeTelegramConfig(agentDir, configPath, nextConfig);
    },
  };
}

export type TelegramAuthorizationState =
  | { kind: "pair"; userId: number }
  | { kind: "allow" }
  | { kind: "deny" };

export interface TelegramUserPairingDeps<TContext> {
  allowedUserId?: number;
  ctx: TContext;
  setAllowedUserId: (userId: number) => void;
  persistConfig: () => Promise<void>;
  updateStatus: (ctx: TContext) => void;
}

export interface TelegramUserPairingRuntimeDeps<TContext> {
  getAllowedUserId: () => number | undefined;
  setAllowedUserId: (userId: number) => void;
  persistConfig: () => Promise<void>;
  updateStatus: (ctx: TContext) => void;
}

export interface TelegramUserPairingRuntime<TContext> {
  pairIfNeeded: (userId: number, ctx: TContext) => Promise<boolean>;
}

export function getTelegramAuthorizationState(
  userId: number,
  allowedUserId?: number,
): TelegramAuthorizationState {
  if (allowedUserId === undefined) {
    return { kind: "pair", userId };
  }
  if (userId === allowedUserId) {
    return { kind: "allow" };
  }
  return { kind: "deny" };
}

export async function pairTelegramUserIfNeeded<TContext>(
  userId: number,
  deps: TelegramUserPairingDeps<TContext>,
): Promise<boolean> {
  const authorization = getTelegramAuthorizationState(
    userId,
    deps.allowedUserId,
  );
  if (authorization.kind !== "pair") return false;
  deps.setAllowedUserId(authorization.userId);
  await deps.persistConfig();
  deps.updateStatus(deps.ctx);
  return true;
}

export function createTelegramUserPairingRuntime<TContext>(
  deps: TelegramUserPairingRuntimeDeps<TContext>,
): TelegramUserPairingRuntime<TContext> {
  return {
    pairIfNeeded: (userId, ctx) =>
      pairTelegramUserIfNeeded(userId, {
        allowedUserId: deps.getAllowedUserId(),
        ctx,
        setAllowedUserId: deps.setAllowedUserId,
        persistConfig: deps.persistConfig,
        updateStatus: deps.updateStatus,
      }),
  };
}


/**
 * Telegram setup prompt helpers
 * Computes token-prefill defaults and prompt mode selection for /telegram-setup
 */

export interface TelegramSetupConfig {
  botToken?: string;
  botId?: number;
  botUsername?: string;
  allowedUserId?: number;
  lastUpdateId?: number;
}

export interface TelegramBotTokenPromptSpec {
  method: "input" | "editor";
  value: string;
}

export interface TelegramSetupUser {
  id: number;
  username?: string;
}

export interface TelegramPollingStartResult {
  ok: boolean;
  message?: string;
}

export interface TelegramSetupDeps {
  hasUI: boolean;
  env: NodeJS.ProcessEnv;
  config: TelegramSetupConfig;
  promptInput: (label: string, value: string) => Promise<string | undefined>;
  promptEditor: (label: string, value: string) => Promise<string | undefined>;
  getMe: (botToken: string) => Promise<{
    ok: boolean;
    result?: TelegramSetupUser;
    description?: string;
  }>;
  persistConfig: (config: TelegramSetupConfig) => Promise<void>;
  notify: (message: string, level: "info" | "error") => void;
  startPolling: () => unknown | Promise<unknown>;
  updateStatus: () => void;
}

export interface TelegramSetupPromptContext {
  hasUI: boolean;
  ui: {
    input: (label: string, value: string) => Promise<string | undefined>;
    editor: (label: string, value: string) => Promise<string | undefined>;
    notify: (message: string, level: "info" | "error") => void;
  };
}

export interface TelegramSetupGuard {
  start: () => boolean;
  finish: () => void;
}

export interface TelegramSetupPromptRuntimeDeps<
  TContext extends TelegramSetupPromptContext,
> {
  env?: NodeJS.ProcessEnv;
  getConfig: () => TelegramSetupConfig;
  setConfig: (config: TelegramSetupConfig) => void;
  setupGuard: TelegramSetupGuard;
  getMe: TelegramSetupDeps["getMe"];
  persistConfig: (config: TelegramSetupConfig) => Promise<void>;
  startPolling: (ctx: TContext) => unknown | Promise<unknown>;
  updateStatus: (ctx: TContext) => void;
  recordRuntimeEvent?: (
    category: string,
    error: unknown,
    details?: Record<string, unknown>,
  ) => void;
}

export const TELEGRAM_BOT_TOKEN_INPUT_PLACEHOLDER = "123456:ABCDEF...";
const TELEGRAM_BOT_TOKEN_ENV_VARS = [
  "TELEGRAM_BOT_TOKEN",
  "TELEGRAM_BOT_KEY",
  "TELEGRAM_TOKEN",
  "TELEGRAM_KEY",
] as const;

function isTelegramPollingStartResult(
  value: unknown,
): value is TelegramPollingStartResult {
  return !!value && typeof value === "object" && typeof (value as { ok?: unknown }).ok === "boolean";
}

export function getTelegramBotTokenInputDefault(
  env: NodeJS.ProcessEnv = process.env,
  configToken?: string,
): string {
  const trimmedConfigToken = configToken?.trim();
  if (trimmedConfigToken) return trimmedConfigToken;
  for (const key of TELEGRAM_BOT_TOKEN_ENV_VARS) {
    const value = env[key]?.trim();
    if (value) return value;
  }
  return TELEGRAM_BOT_TOKEN_INPUT_PLACEHOLDER;
}

export function getTelegramBotTokenPromptSpec(
  env: NodeJS.ProcessEnv = process.env,
  configToken?: string,
): TelegramBotTokenPromptSpec {
  const value = getTelegramBotTokenInputDefault(env, configToken);
  return {
    method: value === TELEGRAM_BOT_TOKEN_INPUT_PLACEHOLDER ? "input" : "editor",
    value,
  };
}

export async function runTelegramSetup(
  deps: TelegramSetupDeps,
): Promise<TelegramSetupConfig | undefined> {
  if (!deps.hasUI) return undefined;
  const tokenPrompt = getTelegramBotTokenPromptSpec(
    deps.env,
    deps.config.botToken,
  );
  const token =
    tokenPrompt.method === "editor"
      ? await deps.promptEditor("Telegram bot token", tokenPrompt.value)
      : await deps.promptInput("Telegram bot token", tokenPrompt.value);
  if (!token) return undefined;
  const nextConfig: TelegramSetupConfig = {
    ...deps.config,
    botToken: token.trim(),
  };
  const data = await deps.getMe(nextConfig.botToken ?? "");
  if (!data.ok || !data.result) {
    deps.notify(data.description || "Invalid Telegram bot token", "error");
    return undefined;
  }
  nextConfig.botId = data.result.id;
  nextConfig.botUsername = data.result.username;
  await deps.persistConfig(nextConfig);
  deps.notify(
    `Telegram bot connected: @${nextConfig.botUsername ?? "unknown"}`,
    "info",
  );
  deps.notify(
    "Send /start to your bot in Telegram to pair this extension with your account.",
    "info",
  );
  const startResult = await deps.startPolling();
  if (isTelegramPollingStartResult(startResult) && startResult.message) {
    deps.notify(startResult.message, startResult.ok ? "info" : "error");
  }
  deps.updateStatus();
  return nextConfig;
}

export function createTelegramSetupPromptRuntime<
  TContext extends TelegramSetupPromptContext,
>(deps: TelegramSetupPromptRuntimeDeps<TContext>) {
  return async function promptForConfig(ctx: TContext): Promise<void> {
    if (!ctx.hasUI || !deps.setupGuard.start()) return;
    try {
      const nextConfig = await runTelegramSetup({
        hasUI: ctx.hasUI,
        env: deps.env ?? process.env,
        config: deps.getConfig(),
        promptInput: (label, value) => ctx.ui.input(label, value),
        promptEditor: (label, value) => ctx.ui.editor(label, value),
        getMe: deps.getMe,
        persistConfig: deps.persistConfig,
        notify: (message, level) => ctx.ui.notify(message, level),
        startPolling: () => deps.startPolling(ctx),
        updateStatus: () => deps.updateStatus(ctx),
      });
      if (nextConfig) deps.setConfig(nextConfig);
    } catch (error) {
      deps.recordRuntimeEvent?.("setup", error);
      throw error;
    } finally {
      deps.setupGuard.finish();
    }
  };
}


**Checking process status**

I need to find out if the current session owns pid 10916. It seems possible that the API agent process isn't related to the pi CLI. Since messages are coming through a Telegram bridge extension in the system prompt, I wonder if the tool can show the current working directory, which looks like /home/sebas. I might need to inspect the pi status from the command. It could be tricky if the `pi` interactive mode is not possible, but I can check TTY/session details for 10916.

ps -p 10916 -o pid,ppid,tty,stat,lstart,cmd

readlink /proc/10916/cwd && echo '--- environ ---' && tr '\0' '\n' < /proc/10916/environ | grep -E 'PI_CODING_AGENT_DIR|TELEGRAM|PWD|TERM' | sort

ls -ltr /home/sebas/pi-config/sessions/--home-sebas-- | tail -n 5


    PID    PPID TT       STAT                  STARTED CMD
  10916    2117 pts/0    Sl+  Tue May  5 10:38:20 2026 pi


/home/sebas
--- environ ---
COLORTERM=truecolor
PI_CODING_AGENT_DIR=/mnt/rpi/pi-config
PWD=/home/sebas
TERM_PROGRAM=WarpTerminal
TERM=xterm-256color


-rw-r--r-- 1 sebas sebas  152220 May  5 09:07 2026-05-04T20-31-12-064Z_019df4b0-0ebf-777a-8efb-4a9a002cf97c.jsonl
-rw-r--r-- 1 sebas sebas   31192 May  5 10:42 2026-05-05T13-38-23-930Z_019df85c-7c39-73e8-aa53-3b548239898e.jsonl
-rw-r--r-- 1 sebas sebas  561516 May  5 12:11 2026-05-05T13-44-38-590Z_019df862-33be-7759-b796-3d6b48b23493.jsonl
-rw-r--r-- 1 sebas sebas  127813 May  5 12:28 2026-05-05T14-28-30-272Z_019df88a-5bbf-76ca-ac41-3144553f965a.jsonl
-rw-r--r-- 1 sebas sebas  198745 May  5 13:33 2026-05-01T20-20-59-838Z_019de533-a33e-7348-809e-ac73ede080fb.jsonl


**Inspecting configuration files**

I find it interesting that there are two potentially conflicting paths for configuration: PI_CODING_AGENT_DIR pointing to /mnt/rpi/pi-config and an earlier lock/config at /home/sebas/pi-config. That could lead to some mismatches in command execution! I really need to check the contents of locks.json and telegram.json in the /mnt/rpi/pi-config directory to ensure everything aligns properly. I just want to make sure I’ve got a clear view of the setup!

ls -l /mnt/rpi/pi-config/telegram.json /mnt/rpi/pi-config/locks.json 2>/dev/null && echo '--- /mnt config ---' && cat /mnt/rpi/pi-config/telegram.json && echo '--- /mnt locks ---' && cat /mnt/rpi/pi-config/locks.json


(no output)

Command exited with code 2


ls -la /mnt/rpi/pi-config | sed -n '1,120p' && echo '--- maybe telegram/locks ---' && ls -la /mnt/rpi/pi-config/telegram.json /mnt/rpi/pi-config/locks.json 2>&1


--- maybe telegram/locks ---
ls: cannot access '/mnt/rpi/pi-config': No such file or directory
ls: cannot access '/mnt/rpi/pi-config/telegram.json': No such file or directory
ls: cannot access '/mnt/rpi/pi-config/locks.json': No such file or directory

Command exited with code 2


**Troubleshooting configuration issues**

I see that the environment points to a non-existent directory, which is causing some confusion since the actual config is located in a different path. I’m noticing that the PID environment variable points to an incorrect path. It seems there’s a mismatch between our tool shell and the Raspberry Pi process. This discrepancy might be the root cause of the issue, so I need to verify the current symlink or script that sets the environment variable.

grep -Rni "PI_CODING_AGENT_DIR" /home/sebas/.bashrc /home/sebas/.profile /home/sebas/pi-config /home/sebas/.config /home/sebas/.local 2>/dev/null | sed -n '1,200p'


n      config.allowedUserId = userId;\n    },\n    load: async () => {\n      config = await readTelegramConfig(configPath);\n    },\n    persist: async (nextConfig = config) => {\n      await writeTelegramConfig(agentDir, configPath, nextConfig);\n    },\n  };\n}\n\nexport type TelegramAuthorizationState =\n  | { kind: \"pair\"; userId: number }\n  | { kind: \"allow\" }\n  | { kind: \"deny\" };\n\nexport interface TelegramUserPairingDeps<TContext> {\n  allowedUserId?: number;\n  ctx: TContext;\n  setAllowedUserId: (userId: number) => void;\n  persistConfig: () => Promise<void>;\n  updateStatus: (ctx: TContext) => void;\n}\n\nexport interface TelegramUserPairingRuntimeDeps<TContext> {\n  getAllowedUserId: () => number | undefined;\n  setAllowedUserId: (userId: number) => void;\n  persistConfig: () => Promise<void>;\n  updateStatus: (ctx: TContext) => void;\n}\n\nexport interface TelegramUserPairingRuntime<TContext> {\n  pairIfNeeded: (userId: number, ctx: TContext) => Promise<boolean>;\n}\n\nexport function getTelegramAuthorizationState(\n  userId: number,\n  allowedUserId?: number,\n): TelegramAuthorizationState {\n  if (allowedUserId === undefined) {\n    return { kind: \"pair\", userId };\n  }\n  if (userId === allowedUserId) {\n    return { kind: \"allow\" };\n  }\n  return { kind: \"deny\" };\n}\n\nexport async function pairTelegramUserIfNeeded<TContext>(\n  userId: number,\n  deps: TelegramUserPairingDeps<TContext>,\n): Promise<boolean> {\n  const authorization = getTelegramAuthorizationState(\n    userId,\n    deps.allowedUserId,\n  );\n  if (authorization.kind !== \"pair\") return false;\n  deps.setAllowedUserId(authorization.userId);\n  await deps.persistConfig();\n  deps.updateStatus(deps.ctx);\n  return true;\n}\n\nexport function createTelegramUserPairingRuntime<TContext>(\n  deps: TelegramUserPairingRuntimeDeps<TContext>,\n): TelegramUserPairingRuntime<TContext> {\n  return {\n    pairIfNeeded: (userId, ctx) =>\n      pairTelegramUserIfNeeded(userId, {\n        allowedUserId: deps.getAllowedUserId(),\n        ctx,\n        setAllowedUserId: deps.setAllowedUserId,\n        persistConfig: deps.persistConfig,\n        updateStatus: deps.updateStatus,\n      }),\n  };\n}\n"
/home/sebas/pi-config/sessions/session-memory-jobs/20260422T032521567405Z.json:813:              "text": "pi - AI coding assistant with read, bash, edit, write tools\n\nUsage:\n  pi [options] [@files...] [messages...]\n\nCommands:\n  pi install <source> [-l]     Install extension source and add to settings\n  pi remove <source> [-l]      Remove extension source from settings\n  pi uninstall <source> [-l]   Alias for remove\n  pi update [source]           Update installed extensions (skips pinned sources)\n  pi list                      List installed extensions from settings\n  pi config                    Open TUI to enable/disable package resources\n  pi <command> --help          Show help for install/remove/uninstall/update/list\n\nOptions:\n  --provider <name>              Provider name (default: google)\n  --model <pattern>              Model pattern or ID (supports \"provider/id\" and optional \":<thinking>\")\n  --api-key <key>                API key (defaults to env vars)\n  --system-prompt <text>         System prompt (default: coding assistant prompt)\n  --append-system-prompt <text>  Append text or file contents to the system prompt (can be used multiple times)\n  --mode <mode>                  Output mode: text (default), json, or rpc\n  --print, -p                    Non-interactive mode: process prompt and exit\n  --continue, -c                 Continue previous session\n  --resume, -r                   Select a session to resume\n  --session <path|id>            Use specific session file or partial UUID\n  --fork <path|id>               Fork specific session file or partial UUID into a new session\n  --session-dir <dir>            Directory for session storage and lookup\n  --no-session                   Don't save session (ephemeral)\n  --models <patterns>            Comma-separated model patterns for Ctrl+P cycling\n                                 Supports globs (anthropic/*, *sonnet*) and fuzzy matching\n  --no-tools                     Disable all tools by default (built-in and extension)\n  --tools <tools>                Comma-separated allowlist of tool names to enable\n                                 Applies to built-in and extension tools\n  --thinking <level>             Set thinking level: off, minimal, low, medium, high, xhigh\n  --extension, -e <path>         Load an extension file (can be used multiple times)\n  --no-extensions, -ne           Disable extension discovery (explicit -e paths still work)\n  --skill <path>                 Load a skill file or directory (can be used multiple times)\n  --no-skills, -ns               Disable skills discovery and loading\n  --prompt-template <path>       Load a prompt template file or directory (can be used multiple times)\n  --no-prompt-templates, -np     Disable prompt template discovery and loading\n  --theme <path>                 Load a theme file or directory (can be used multiple times)\n  --no-themes                    Disable theme discovery and loading\n  --no-context-files, -nc        Disable AGENTS.md and CLAUDE.md discovery and loading\n  --export <file>                Export session file to HTML and exit\n  --list-models [search]         List available models (with optional fuzzy search)\n  --verbose                      Force verbose startup (overrides quietStartup setting)\n  --offline                      Disable startup network operations (same as PI_OFFLINE=1)\n  --help, -h                     Show this help\n  --version, -v                  Show version number\n\nExtensions can register additional flags (e.g., --plan from plan-mode extension).\nExtension CLI Flags:\n  --switch-session            Open the session-switch picker after startup, then relaunch into the selected session\n\n\nExamples:\n  # Interactive mode\n  pi\n\n  # Interactive mode with initial prompt\n  pi \"List all .ts files in src/\"\n\n  # Include files in initial message\n  pi @prompt.md @image.png \"What color is the sky?\"\n\n  # Non-interactive mode (process and exit)\n  pi -p \"List all .ts files in src/\"\n\n  # Multiple messages (interactive)\n  pi \"Read package.json\" \"What dependencies do we have?\"\n\n  # Continue previous session\n  pi --continue \"What did we discuss?\"\n\n  # Use different model\n  pi --provider openai --model gpt-4o-mini \"Help me refactor this code\"\n\n  # Use model with provider prefix (no --provider needed)\n  pi --model openai/gpt-4o \"Help me refactor this code\"\n\n  # Use model with thinking level shorthand\n  pi --model sonnet:high \"Solve this complex problem\"\n\n  # Limit model cycling to specific models\n  pi --models claude-sonnet,claude-haiku,gpt-4o\n\n  # Limit to a specific provider with glob pattern\n  pi --models \"github-copilot/*\"\n\n  # Cycle models with fixed thinking levels\n  pi --models sonnet:high,haiku:low\n\n  # Start with a specific thinking level\n  pi --thinking high \"Solve this complex problem\"\n\n  # Read-only mode (no file modifications possible)\n  pi --tools read,grep,find,ls -p \"Review the code in src/\"\n\n  # Export a session file to HTML\n  pi --export ~/.pi/agent/sessions/--path--/session.jsonl\n  pi --export session.jsonl output.html\n\nEnvironment Variables:\n  ANTHROPIC_API_KEY                - Anthropic Claude API key\n  ANTHROPIC_OAUTH_TOKEN            - Anthropic OAuth token (alternative to API key)\n  OPENAI_API_KEY                   - OpenAI GPT API key\n  AZURE_OPENAI_API_KEY             - Azure OpenAI API key\n  AZURE_OPENAI_BASE_URL            - Azure OpenAI base URL (https://{resource}.openai.azure.com/openai/v1)\n  AZURE_OPENAI_RESOURCE_NAME       - Azure OpenAI resource name (alternative to base URL)\n  AZURE_OPENAI_API_VERSION         - Azure OpenAI API version (default: v1)\n  AZURE_OPENAI_DEPLOYMENT_NAME_MAP - Azure OpenAI model=deployment map (comma-separated)\n  GEMINI_API_KEY                   - Google Gemini API key\n  GROQ_API_KEY                     - Groq API key\n  CEREBRAS_API_KEY                 - Cerebras API key\n  XAI_API_KEY                      - xAI Grok API key\n  OPENROUTER_API_KEY               - OpenRouter API key\n  AI_GATEWAY_API_KEY               - Vercel AI Gateway API key\n  ZAI_API_KEY                      - ZAI API key\n  MISTRAL_API_KEY                  - Mistral API key\n  MINIMAX_API_KEY                  - MiniMax API key\n  OPENCODE_API_KEY                 - OpenCode Zen/OpenCode Go API key\n  KIMI_API_KEY                     - Kimi For Coding API key\n  AWS_PROFILE                      - AWS profile for Amazon Bedrock\n  AWS_ACCESS_KEY_ID                - AWS access key for Amazon Bedrock\n  AWS_SECRET_ACCESS_KEY            - AWS secret key for Amazon Bedrock\n  AWS_BEARER_TOKEN_BEDROCK         - Bedrock API key (bearer token)\n  AWS_REGION                       - AWS region for Amazon Bedrock (e.g., us-east-1)\n  PI_CODING_AGENT_DIR              - Session storage directory (default: ~/.pi/agent)\n  PI_PACKAGE_DIR                   - Override package directory (for Nix/Guix store paths)\n  PI_OFFLINE                       - Disable startup network operations when set to 1/true/yes\n  PI_TELEMETRY                     - Override install telemetry when set to 1/true/yes or 0/false/no\n  PI_SHARE_VIEWER_URL              - Base URL for /share command (default: https://pi.dev/session/)\n  PI_AI_ANTIGRAVITY_VERSION        - Override Antigravity User-Agent version (e.g., 1.23.0)\n\nBuilt-in Tool Names:\n  read   - Read file contents\n  bash   - Execute bash commands\n  edit   - Edit files with find/replace\n  write  - Write files (creates/overwrites)\n  grep   - Search file contents (read-only, off by default)\n  find   - Find files by glob pattern (read-only, off by default)\n  ls     - List directory contents (read-only, off by default)\n\n"
/home/sebas/pi-config/sessions/session-memory-jobs/20260422T032521567405Z.json:832:              "text": "pi - AI coding assistant with read, bash, edit, write tools\n\nUsage:\n  pi [options] [@files...] [messages...]\n\nCommands:\n  pi install <source> [-l]     Install extension source and add to settings\n  pi remove <source> [-l]      Remove extension source from settings\n  pi uninstall <source> [-l]   Alias for remove\n  pi update [source]           Update installed extensions (skips pinned sources)\n  pi list                      List installed extensions from settings\n  pi config                    Open TUI to enable/disable package resources\n  pi <command> --help          Show help for install/remove/uninstall/update/list\n\nOptions:\n  --provider <name>              Provider name (default: google)\n  --model <pattern>              Model pattern or ID (supports \"provider/id\" and optional \":<thinking>\")\n  --api-key <key>                API key (defaults to env vars)\n  --system-prompt <text>         System prompt (default: coding assistant prompt)\n  --append-system-prompt <text>  Append text or file contents to the system prompt (can be used multiple times)\n  --mode <mode>                  Output mode: text (default), json, or rpc\n  --print, -p                    Non-interactive mode: process prompt and exit\n  --continue, -c                 Continue previous session\n  --resume, -r                   Select a session to resume\n  --session <path|id>            Use specific session file or partial UUID\n  --fork <path|id>               Fork specific session file or partial UUID into a new session\n  --session-dir <dir>            Directory for session storage and lookup\n  --no-session                   Don't save session (ephemeral)\n  --models <patterns>            Comma-separated model patterns for Ctrl+P cycling\n                                 Supports globs (anthropic/*, *sonnet*) and fuzzy matching\n  --no-tools                     Disable all tools by default (built-in and extension)\n  --tools <tools>                Comma-separated allowlist of tool names to enable\n                                 Applies to built-in and extension tools\n  --thinking <level>             Set thinking level: off, minimal, low, medium, high, xhigh\n  --extension, -e <path>         Load an extension file (can be used multiple times)\n  --no-extensions, -ne           Disable extension discovery (explicit -e paths still work)\n  --skill <path>                 Load a skill file or directory (can be used multiple times)\n  --no-skills, -ns               Disable skills discovery and loading\n  --prompt-template <path>       Load a prompt template file or directory (can be used multiple times)\n  --no-prompt-templates, -np     Disable prompt template discovery and loading\n  --theme <path>                 Load a theme file or directory (can be used multiple times)\n  --no-themes                    Disable theme discovery and loading\n  --no-context-files, -nc        Disable AGENTS.md and CLAUDE.md discovery and loading\n  --export <file>                Export session file to HTML and exit\n  --list-models [search]         List available models (with optional fuzzy search)\n  --verbose                      Force verbose startup (overrides quietStartup setting)\n  --offline                      Disable startup network operations (same as PI_OFFLINE=1)\n  --help, -h                     Show this help\n  --version, -v                  Show version number\n\nExtensions can register additional flags (e.g., --plan from plan-mode extension).\nExtension CLI Flags:\n  --switch-session            Open the session-switch picker after startup, then relaunch into the selected session\n\n\nExamples:\n  # Interactive mode\n  pi\n\n  # Interactive mode with initial prompt\n  pi \"List all .ts files in src/\"\n\n  # Include files in initial message\n  pi @prompt.md @image.png \"What color is the sky?\"\n\n  # Non-interactive mode (process and exit)\n  pi -p \"List all .ts files in src/\"\n\n  # Multiple messages (interactive)\n  pi \"Read package.json\" \"What dependencies do we have?\"\n\n  # Continue previous session\n  pi --continue \"What did we discuss?\"\n\n  # Use different model\n  pi --provider openai --model gpt-4o-mini \"Help me refactor this code\"\n\n  # Use model with provider prefix (no --provider needed)\n  pi --model openai/gpt-4o \"Help me refactor this code\"\n\n  # Use model with thinking level shorthand\n  pi --model sonnet:high \"Solve this complex problem\"\n\n  # Limit model cycling to specific models\n  pi --models claude-sonnet,claude-haiku,gpt-4o\n\n  # Limit to a specific provider with glob pattern\n  pi --models \"github-copilot/*\"\n\n  # Cycle models with fixed thinking levels\n  pi --models sonnet:high,haiku:low\n\n  # Start with a specific thinking level\n  pi --thinking high \"Solve this complex problem\"\n\n  # Read-only mode (no file modifications possible)\n  pi --tools read,grep,find,ls -p \"Review the code in src/\"\n\n  # Export a session file to HTML\n  pi --export ~/.pi/agent/sessions/--path--/session.jsonl\n  pi --export session.jsonl output.html\n\nEnvironment Variables:\n  ANTHROPIC_API_KEY                - Anthropic Claude API key\n  ANTHROPIC_OAUTH_TOKEN            - Anthropic OAuth token (alternative to API key)\n  OPENAI_API_KEY                   - OpenAI GPT API key\n  AZURE_OPENAI_API_KEY             - Azure OpenAI API key\n  AZURE_OPENAI_BASE_URL            - Azure OpenAI base URL (https://{resource}.openai.azure.com/openai/v1)\n  AZURE_OPENAI_RESOURCE_NAME       - Azure OpenAI resource name (alternative to base URL)\n  AZURE_OPENAI_API_VERSION         - Azure OpenAI API version (default: v1)\n  AZURE_OPENAI_DEPLOYMENT_NAME_MAP - Azure OpenAI model=deployment map (comma-separated)\n  GEMINI_API_KEY                   - Google Gemini API key\n  GROQ_API_KEY                     - Groq API key\n  CEREBRAS_API_KEY                 - Cerebras API key\n  XAI_API_KEY                      - xAI Grok API key\n  OPENROUTER_API_KEY               - OpenRouter API key\n  AI_GATEWAY_API_KEY               - Vercel AI Gateway API key\n  ZAI_API_KEY                      - ZAI API key\n  MISTRAL_API_KEY                  - Mistral API key\n  MINIMAX_API_KEY                  - MiniMax API key\n  OPENCODE_API_KEY                 - OpenCode Zen/OpenCode Go API key\n  KIMI_API_KEY                     - Kimi For Coding API key\n  AWS_PROFILE                      - AWS profile for Amazon Bedrock\n  AWS_ACCESS_KEY_ID                - AWS access key for Amazon Bedrock\n  AWS_SECRET_ACCESS_KEY            - AWS secret key for Amazon Bedrock\n  AWS_BEARER_TOKEN_BEDROCK         - Bedrock API key (bearer token)\n  AWS_REGION                       - AWS region for Amazon Bedrock (e.g., us-east-1)\n  PI_CODING_AGENT_DIR              - Session storage directory (default: ~/.pi/agent)\n  PI_PACKAGE_DIR                   - Override package directory (for Nix/Guix store paths)\n  PI_OFFLINE                       - Disable startup network operations when set to 1/true/yes\n  PI_TELEMETRY                     - Override install telemetry when set to 1/true/yes or 0/false/no\n  PI_SHARE_VIEWER_URL              - Base URL for /share command (default: https://pi.dev/session/)\n  PI_AI_ANTIGRAVITY_VERSION        - Override Antigravity User-Agent version (e.g., 1.23.0)\n\nBuilt-in Tool Names:\n  read   - Read file contents\n  bash   - Execute bash commands\n  edit   - Edit files with find/replace\n  write  - Write files (creates/overwrites)\n  grep   - Search file contents (read-only, off by default)\n  find   - Find files by glob pattern (read-only, off by default)\n  ls     - List directory contents (read-only, off by default)\n\n"
/home/sebas/pi-config/sessions/session-memory-jobs/20260501T033642290422Z.json:853:              "text": "/home/sebas/.npm-global/bin/pi\nWarning: No models match pattern \"nvidia/nvidia/nemotron-4-340b-instruct\"\npi - AI coding assistant with read, bash, edit, write tools\n\nUsage:\n  pi [options] [@files...] [messages...]\n\nCommands:\n  pi install <source> [-l]     Install extension source and add to settings\n  pi remove <source> [-l]      Remove extension source from settings\n  pi uninstall <source> [-l]   Alias for remove\n  pi update [source|self|pi]   Update pi and installed extensions\n  pi list                      List installed extensions from settings\n  pi config                    Open TUI to enable/disable package resources\n  pi <command> --help          Show help for install/remove/uninstall/update/list\n\nOptions:\n  --provider <name>              Provider name (default: google)\n  --model <pattern>              Model pattern or ID (supports \"provider/id\" and optional \":<thinking>\")\n  --api-key <key>                API key (defaults to env vars)\n  --system-prompt <text>         System prompt (default: coding assistant prompt)\n  --append-system-prompt <text>  Append text or file contents to the system prompt (can be used multiple times)\n  --mode <mode>                  Output mode: text (default), json, or rpc\n  --print, -p                    Non-interactive mode: process prompt and exit\n  --continue, -c                 Continue previous session\n  --resume, -r                   Select a session to resume\n  --session <path|id>            Use specific session file or partial UUID\n  --fork <path|id>               Fork specific session file or partial UUID into a new session\n  --session-dir <dir>            Directory for session storage and lookup\n  --no-session                   Don't save session (ephemeral)\n  --models <patterns>            Comma-separated model patterns for Ctrl+P cycling\n                                 Supports globs (anthropic/*, *sonnet*) and fuzzy matching\n  --no-tools, -nt                Disable all tools by default (built-in and extension)\n  --no-builtin-tools, -nbt       Disable built-in tools by default but keep extension/custom tools enabled\n  --tools, -t <tools>            Comma-separated allowlist of tool names to enable\n                                 Applies to built-in, extension, and custom tools\n  --thinking <level>             Set thinking level: off, minimal, low, medium, high, xhigh\n  --extension, -e <path>         Load an extension file (can be used multiple times)\n  --no-extensions, -ne           Disable extension discovery (explicit -e paths still work)\n  --skill <path>                 Load a skill file or directory (can be used multiple times)\n  --no-skills, -ns               Disable skills discovery and loading\n  --prompt-template <path>       Load a prompt template file or directory (can be used multiple times)\n  --no-prompt-templates, -np     Disable prompt template discovery and loading\n  --theme <path>                 Load a theme file or directory (can be used multiple times)\n  --no-themes                    Disable theme discovery and loading\n  --no-context-files, -nc        Disable AGENTS.md and CLAUDE.md discovery and loading\n  --export <file>                Export session file to HTML and exit\n  --list-models [search]         List available models (with optional fuzzy search)\n  --verbose                      Force verbose startup (overrides quietStartup setting)\n  --offline                      Disable startup network operations (same as PI_OFFLINE=1)\n  --help, -h                     Show this help\n  --version, -v                  Show version number\n\nExtensions can register additional flags (e.g., --plan from plan-mode extension).\nExtension CLI Flags:\n  --switch-session            Open the session-switch picker after startup, then relaunch into the selected session\n\n\nExamples:\n  # Interactive mode\n  pi\n\n  # Interactive mode with initial prompt\n  pi \"List all .ts files in src/\"\n\n  # Include files in initial message\n  pi @prompt.md @image.png \"What color is the sky?\"\n\n  # Non-interactive mode (process and exit)\n  pi -p \"List all .ts files in src/\"\n\n  # Multiple messages (interactive)\n  pi \"Read package.json\" \"What dependencies do we have?\"\n\n  # Continue previous session\n  pi --continue \"What did we discuss?\"\n\n  # Use different model\n  pi --provider openai --model gpt-4o-mini \"Help me refactor this code\"\n\n  # Use model with provider prefix (no --provider needed)\n  pi --model openai/gpt-4o \"Help me refactor this code\"\n\n  # Use model with thinking level shorthand\n  pi --model sonnet:high \"Solve this complex problem\"\n\n  # Limit model cycling to specific models\n  pi --models claude-sonnet,claude-haiku,gpt-4o\n\n  # Limit to a specific provider with glob pattern\n  pi --models \"github-copilot/*\"\n\n  # Cycle models with fixed thinking levels\n  pi --models sonnet:high,haiku:low\n\n  # Start with a specific thinking level\n  pi --thinking high \"Solve this complex problem\"\n\n  # Read-only mode (no file modifications possible)\n  pi --tools read,grep,find,ls -p \"Review the code in src/\"\n\n  # Export a session file to HTML\n  pi --export ~/.pi/agent/sessions/--path--/session.jsonl\n  pi --export session.jsonl output.html\n\nEnvironment Variables:\n  ANTHROPIC_API_KEY                - Anthropic Claude API key\n  ANTHROPIC_OAUTH_TOKEN            - Anthropic OAuth token (alternative to API key)\n  OPENAI_API_KEY                   - OpenAI GPT API key\n  AZURE_OPENAI_API_KEY             - Azure OpenAI API key\n  AZURE_OPENAI_BASE_URL            - Azure OpenAI/Cognitive Services base URL (e.g. https://{resource}.openai.azure.com)\n  AZURE_OPENAI_RESOURCE_NAME       - Azure OpenAI resource name (alternative to base URL)\n  AZURE_OPENAI_API_VERSION         - Azure OpenAI API version (default: v1)\n  AZURE_OPENAI_DEPLOYMENT_NAME_MAP - Azure OpenAI model=deployment map (comma-separated)\n  DEEPSEEK_API_KEY                 - DeepSeek API key\n  GEMINI_API_KEY                   - Google Gemini API key\n  GROQ_API_KEY                     - Groq API key\n  CEREBRAS_API_KEY                 - Cerebras API key\n  XAI_API_KEY                      - xAI Grok API key\n  FIREWORKS_API_KEY                - Fireworks API key\n  OPENROUTER_API_KEY               - OpenRouter API key\n  AI_GATEWAY_API_KEY               - Vercel AI Gateway API key\n  ZAI_API_KEY                      - ZAI API key\n  MISTRAL_API_KEY                  - Mistral API key\n  MINIMAX_API_KEY                  - MiniMax API key\n  OPENCODE_API_KEY                 - OpenCode Zen/OpenCode Go API key\n  KIMI_API_KEY                     - Kimi For Coding API key\n  CLOUDFLARE_API_KEY               - Cloudflare API token (Workers AI)\n  CLOUDFLARE_ACCOUNT_ID            - Cloudflare account id (required for Workers AI)\n  AWS_PROFILE                      - AWS profile for Amazon Bedrock\n  AWS_ACCESS_KEY_ID                - AWS access key for Amazon Bedrock\n  AWS_SECRET_ACCESS_KEY            - AWS secret key for Amazon Bedrock\n  AWS_BEARER_TOKEN_BEDROCK         - Bedrock API key (bearer token)\n  AWS_REGION                       - AWS region for Amazon Bedrock (e.g., us-east-1)\n  PI_CODING_AGENT_DIR              - Session storage directory (default: ~/.pi/agent)\n  PI_PACKAGE_DIR                   - Override package directory (for Nix/Guix store paths)\n  PI_OFFLINE                       - Disable startup network operations when set to 1/true/yes\n  PI_TELEMETRY                     - Override install telemetry when set to 1/true/yes or 0/false/no\n  PI_SHARE_VIEWER_URL              - Base URL for /share command (default: https://pi.dev/session/)\n  PI_AI_ANTIGRAVITY_VERSION        - Override Antigravity User-Agent version (e.g., 1.23.0)\n\nBuilt-in Tool Names:\n  read   - Read file contents\n  bash   - Execute bash commands\n  edit   - Edit files with find/replace\n  write  - Write files (creates/overwrites)\n  grep   - Search file contents (read-only, off by default)\n  find   - Find files by glob pattern (read-only, off by default)\n  ls     - List directory contents (read-only, off by default)\n\n"
/home/sebas/pi-config/git/github.com/apmantza/pi-free/node_modules/@mariozechner/pi-coding-agent/dist/config.js.map:1:{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { dirname, join, resolve } from \"path\";\nimport { fileURLToPath } from \"url\";\n\n// =============================================================================\n// Package Detection\n// =============================================================================\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Detect if we're running as a Bun compiled binary.\n * Bun binaries have import.meta.url containing \"$bunfs\", \"~BUN\", or \"%7EBUN\" (Bun's virtual filesystem path)\n */\nexport const isBunBinary =\n\timport.meta.url.includes(\"$bunfs\") || import.meta.url.includes(\"~BUN\") || import.meta.url.includes(\"%7EBUN\");\n\n/** Detect if Bun is the runtime (compiled binary or bun run) */\nexport const isBunRuntime = !!process.versions.bun;\n\n// =============================================================================\n// Install Method Detection\n// =============================================================================\n\nexport type InstallMethod = \"bun-binary\" | \"npm\" | \"pnpm\" | \"yarn\" | \"bun\" | \"unknown\";\n\nexport function detectInstallMethod(): InstallMethod {\n\tif (isBunBinary) {\n\t\treturn \"bun-binary\";\n\t}\n\n\tconst resolvedPath = `${__dirname}\\0${process.execPath || \"\"}`.toLowerCase();\n\n\tif (resolvedPath.includes(\"/pnpm/\") || resolvedPath.includes(\"/.pnpm/\") || resolvedPath.includes(\"\\\\pnpm\\\\\")) {\n\t\treturn \"pnpm\";\n\t}\n\tif (resolvedPath.includes(\"/yarn/\") || resolvedPath.includes(\"/.yarn/\") || resolvedPath.includes(\"\\\\yarn\\\\\")) {\n\t\treturn \"yarn\";\n\t}\n\tif (isBunRuntime) {\n\t\treturn \"bun\";\n\t}\n\tif (resolvedPath.includes(\"/npm/\") || resolvedPath.includes(\"/node_modules/\") || resolvedPath.includes(\"\\\\npm\\\\\")) {\n\t\treturn \"npm\";\n\t}\n\n\treturn \"unknown\";\n}\n\nexport function getUpdateInstruction(packageName: string): string {\n\tconst method = detectInstallMethod();\n\tswitch (method) {\n\t\tcase \"bun-binary\":\n\t\t\treturn `Download from: https://github.com/badlogic/pi-mono/releases/latest`;\n\t\tcase \"pnpm\":\n\t\t\treturn `Run: pnpm install -g ${packageName}`;\n\t\tcase \"yarn\":\n\t\t\treturn `Run: yarn global add ${packageName}`;\n\t\tcase \"bun\":\n\t\t\treturn `Run: bun install -g ${packageName}`;\n\t\tcase \"npm\":\n\t\t\treturn `Run: npm install -g ${packageName}`;\n\t\tdefault:\n\t\t\treturn `Run: npm install -g ${packageName}`;\n\t}\n}\n\n// =============================================================================\n// Package Asset Paths (shipped with executable)\n// =============================================================================\n\n/**\n * Get the base directory for resolving package assets (themes, package.json, README.md, CHANGELOG.md).\n * - For Bun binary: returns the directory containing the executable\n * - For Node.js (dist/): returns __dirname (the dist/ directory)\n * - For tsx (src/): returns parent directory (the package root)\n */\nexport function getPackageDir(): string {\n\t// Allow override via environment variable (useful for Nix/Guix where store paths tokenize poorly)\n\tconst envDir = process.env.PI_PACKAGE_DIR;\n\tif (envDir) {\n\t\tif (envDir === \"~\") return homedir();\n\t\tif (envDir.startsWith(\"~/\")) return homedir() + envDir.slice(1);\n\t\treturn envDir;\n\t}\n\n\tif (isBunBinary) {\n\t\t// Bun binary: process.execPath points to the compiled executable\n\t\treturn dirname(process.execPath);\n\t}\n\t// Node.js: walk up from __dirname until we find package.json\n\tlet dir = __dirname;\n\twhile (dir !== dirname(dir)) {\n\t\tif (existsSync(join(dir, \"package.json\"))) {\n\t\t\treturn dir;\n\t\t}\n\t\tdir = dirname(dir);\n\t}\n\t// Fallback (shouldn't happen)\n\treturn __dirname;\n}\n\n/**\n * Get path to built-in themes directory (shipped with package)\n * - For Bun binary: theme/ next to executable\n * - For Node.js (dist/): dist/modes/interactive/theme/\n * - For tsx (src/): src/modes/interactive/theme/\n */\nexport function getThemesDir(): string {\n\tif (isBunBinary) {\n\t\treturn join(dirname(process.execPath), \"theme\");\n\t}\n\t// Theme is in modes/interactive/theme/ relative to src/ or dist/\n\tconst packageDir = getPackageDir();\n\tconst srcOrDist = existsSync(join(packageDir, \"src\")) ? \"src\" : \"dist\";\n\treturn join(packageDir, srcOrDist, \"modes\", \"interactive\", \"theme\");\n}\n\n/**\n * Get path to HTML export template directory (shipped with package)\n * - For Bun binary: export-html/ next to executable\n * - For Node.js (dist/): dist/core/export-html/\n * - For tsx (src/): src/core/export-html/\n */\nexport function getExportTemplateDir(): string {\n\tif (isBunBinary) {\n\t\treturn join(dirname(process.execPath), \"export-html\");\n\t}\n\tconst packageDir = getPackageDir();\n\tconst srcOrDist = existsSync(join(packageDir, \"src\")) ? \"src\" : \"dist\";\n\treturn join(packageDir, srcOrDist, \"core\", \"export-html\");\n}\n\n/** Get path to package.json */\nexport function getPackageJsonPath(): string {\n\treturn join(getPackageDir(), \"package.json\");\n}\n\n/** Get path to README.md */\nexport function getReadmePath(): string {\n\treturn resolve(join(getPackageDir(), \"README.md\"));\n}\n\n/** Get path to docs directory */\nexport function getDocsPath(): string {\n\treturn resolve(join(getPackageDir(), \"docs\"));\n}\n\n/** Get path to examples directory */\nexport function getExamplesPath(): string {\n\treturn resolve(join(getPackageDir(), \"examples\"));\n}\n\n/** Get path to CHANGELOG.md */\nexport function getChangelogPath(): string {\n\treturn resolve(join(getPackageDir(), \"CHANGELOG.md\"));\n}\n\n// =============================================================================\n// App Config (from package.json piConfig)\n// =============================================================================\n\nconst pkg = JSON.parse(readFileSync(getPackageJsonPath(), \"utf-8\"));\n\nexport const APP_NAME: string = pkg.piConfig?.name || \"pi\";\nexport const CONFIG_DIR_NAME: string = pkg.piConfig?.configDir || \".pi\";\nexport const VERSION: string = pkg.version;\n\n// e.g., PI_CODING_AGENT_DIR or TAU_CODING_AGENT_DIR\nexport const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;\n\nconst DEFAULT_SHARE_VIEWER_URL = \"https://pi.dev/session/\";\n\n/** Get the share viewer URL for a gist ID */\nexport function getShareViewerUrl(gistId: string): string {\n\tconst baseUrl = process.env.PI_SHARE_VIEWER_URL || DEFAULT_SHARE_VIEWER_URL;\n\treturn `${baseUrl}#${gistId}`;\n}\n\n// =============================================================================\n// User Config Paths (~/.pi/agent/*)\n// =============================================================================\n\n/** Get the agent config directory (e.g., ~/.pi/agent/) */\nexport function getAgentDir(): string {\n\tconst envDir = process.env[ENV_AGENT_DIR];\n\tif (envDir) {\n\t\t// Expand tilde to home directory\n\t\tif (envDir === \"~\") return homedir();\n\t\tif (envDir.startsWith(\"~/\")) return homedir() + envDir.slice(1);\n\t\treturn envDir;\n\t}\n\treturn join(homedir(), CONFIG_DIR_NAME, \"agent\");\n}\n\n/** Get path to user's custom themes directory */\nexport function getCustomThemesDir(): string {\n\treturn join(getAgentDir(), \"themes\");\n}\n\n/** Get path to models.json */\nexport function getModelsPath(): string {\n\treturn join(getAgentDir(), \"models.json\");\n}\n\n/** Get path to auth.json */\nexport function getAuthPath(): string {\n\treturn join(getAgentDir(), \"auth.json\");\n}\n\n/** Get path to settings.json */\nexport function getSettingsPath(): string {\n\treturn join(getAgentDir(), \"settings.json\");\n}\n\n/** Get path to tools directory */\nexport function getToolsDir(): string {\n\treturn join(getAgentDir(), \"tools\");\n}\n\n/** Get path to managed binaries directory (fd, rg) */\nexport function getBinDir(): string {\n\treturn join(getAgentDir(), \"bin\");\n}\n\n/** Get path to prompt templates directory */\nexport function getPromptsDir(): string {\n\treturn join(getAgentDir(), \"prompts\");\n}\n\n/** Get path to sessions directory */\nexport function getSessionsDir(): string {\n\treturn join(getAgentDir(), \"sessions\");\n}\n\n/** Get path to debug log file */\nexport function getDebugLogPath(): string {\n\treturn join(getAgentDir(), `${APP_NAME}-debug.log`);\n}\n"]}
/home/sebas/pi-config/git/github.com/apmantza/pi-free/node_modules/@mariozechner/pi-coding-agent/dist/config.d.ts.map:1:{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAYA;;;GAGG;AACH,eAAO,MAAM,WAAW,SACqF,CAAC;AAE9G,gEAAgE;AAChE,eAAO,MAAM,YAAY,SAAyB,CAAC;AAMnD,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS,CAAC;AAEvF,wBAAgB,mBAAmB,IAAI,aAAa,CAqBnD;AAED,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAgBhE;AAMD;;;;;GAKG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAuBtC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAQrC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAO7C;AAED,+BAA+B;AAC/B,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,4BAA4B;AAC5B,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,iCAAiC;AACjC,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,qCAAqC;AACrC,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,+BAA+B;AAC/B,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAQD,eAAO,MAAM,QAAQ,EAAE,MAAmC,CAAC;AAC3D,eAAO,MAAM,eAAe,EAAE,MAAyC,CAAC;AACxE,eAAO,MAAM,OAAO,EAAE,MAAoB,CAAC;AAG3C,eAAO,MAAM,aAAa,QAA+C,CAAC;AAI1E,6CAA6C;AAC7C,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGxD;AAMD,0DAA0D;AAC1D,wBAAgB,WAAW,IAAI,MAAM,CASpC;AAED,iDAAiD;AACjD,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,8BAA8B;AAC9B,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,4BAA4B;AAC5B,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,gCAAgC;AAChC,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,kCAAkC;AAClC,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,sDAAsD;AACtD,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,6CAA6C;AAC7C,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,qCAAqC;AACrC,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,iCAAiC;AACjC,wBAAgB,eAAe,IAAI,MAAM,CAExC","sourcesContent":["import { existsSync, readFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { dirname, join, resolve } from \"path\";\nimport { fileURLToPath } from \"url\";\n\n// =============================================================================\n// Package Detection\n// =============================================================================\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Detect if we're running as a Bun compiled binary.\n * Bun binaries have import.meta.url containing \"$bunfs\", \"~BUN\", or \"%7EBUN\" (Bun's virtual filesystem path)\n */\nexport const isBunBinary =\n\timport.meta.url.includes(\"$bunfs\") || import.meta.url.includes(\"~BUN\") || import.meta.url.includes(\"%7EBUN\");\n\n/** Detect if Bun is the runtime (compiled binary or bun run) */\nexport const isBunRuntime = !!process.versions.bun;\n\n// =============================================================================\n// Install Method Detection\n// =============================================================================\n\nexport type InstallMethod = \"bun-binary\" | \"npm\" | \"pnpm\" | \"yarn\" | \"bun\" | \"unknown\";\n\nexport function detectInstallMethod(): InstallMethod {\n\tif (isBunBinary) {\n\t\treturn \"bun-binary\";\n\t}\n\n\tconst resolvedPath = `${__dirname}\\0${process.execPath || \"\"}`.toLowerCase();\n\n\tif (resolvedPath.includes(\"/pnpm/\") || resolvedPath.includes(\"/.pnpm/\") || resolvedPath.includes(\"\\\\pnpm\\\\\")) {\n\t\treturn \"pnpm\";\n\t}\n\tif (resolvedPath.includes(\"/yarn/\") || resolvedPath.includes(\"/.yarn/\") || resolvedPath.includes(\"\\\\yarn\\\\\")) {\n\t\treturn \"yarn\";\n\t}\n\tif (isBunRuntime) {\n\t\treturn \"bun\";\n\t}\n\tif (resolvedPath.includes(\"/npm/\") || resolvedPath.includes(\"/node_modules/\") || resolvedPath.includes(\"\\\\npm\\\\\")) {\n\t\treturn \"npm\";\n\t}\n\n\treturn \"unknown\";\n}\n\nexport function getUpdateInstruction(packageName: string): string {\n\tconst method = detectInstallMethod();\n\tswitch (method) {\n\t\tcase \"bun-binary\":\n\t\t\treturn `Download from: https://github.com/badlogic/pi-mono/releases/latest`;\n\t\tcase \"pnpm\":\n\t\t\treturn `Run: pnpm install -g ${packageName}`;\n\t\tcase \"yarn\":\n\t\t\treturn `Run: yarn global add ${packageName}`;\n\t\tcase \"bun\":\n\t\t\treturn `Run: bun install -g ${packageName}`;\n\t\tcase \"npm\":\n\t\t\treturn `Run: npm install -g ${packageName}`;\n\t\tdefault:\n\t\t\treturn `Run: npm install -g ${packageName}`;\n\t}\n}\n\n// =============================================================================\n// Package Asset Paths (shipped with executable)\n// =============================================================================\n\n/**\n * Get the base directory for resolving package assets (themes, package.json, README.md, CHANGELOG.md).\n * - For Bun binary: returns the directory containing the executable\n * - For Node.js (dist/): returns __dirname (the dist/ directory)\n * - For tsx (src/): returns parent directory (the package root)\n */\nexport function getPackageDir(): string {\n\t// Allow override via environment variable (useful for Nix/Guix where store paths tokenize poorly)\n\tconst envDir = process.env.PI_PACKAGE_DIR;\n\tif (envDir) {\n\t\tif (envDir === \"~\") return homedir();\n\t\tif (envDir.startsWith(\"~/\")) return homedir() + envDir.slice(1);\n\t\treturn envDir;\n\t}\n\n\tif (isBunBinary) {\n\t\t// Bun binary: process.execPath points to the compiled executable\n\t\treturn dirname(process.execPath);\n\t}\n\t// Node.js: walk up from __dirname until we find package.json\n\tlet dir = __dirname;\n\twhile (dir !== dirname(dir)) {\n\t\tif (existsSync(join(dir, \"package.json\"))) {\n\t\t\treturn dir;\n\t\t}\n\t\tdir = dirname(dir);\n\t}\n\t// Fallback (shouldn't happen)\n\treturn __dirname;\n}\n\n/**\n * Get path to built-in themes directory (shipped with package)\n * - For Bun binary: theme/ next to executable\n * - For Node.js (dist/): dist/modes/interactive/theme/\n * - For tsx (src/): src/modes/interactive/theme/\n */\nexport function getThemesDir(): string {\n\tif (isBunBinary) {\n\t\treturn join(dirname(process.execPath), \"theme\");\n\t}\n\t// Theme is in modes/interactive/theme/ relative to src/ or dist/\n\tconst packageDir = getPackageDir();\n\tconst srcOrDist = existsSync(join(packageDir, \"src\")) ? \"src\" : \"dist\";\n\treturn join(packageDir, srcOrDist, \"modes\", \"interactive\", \"theme\");\n}\n\n/**\n * Get path to HTML export template directory (shipped with package)\n * - For Bun binary: export-html/ next to executable\n * - For Node.js (dist/): dist/core/export-html/\n * - For tsx (src/): src/core/export-html/\n */\nexport function getExportTemplateDir(): string {\n\tif (isBunBinary) {\n\t\treturn join(dirname(process.execPath), \"export-html\");\n\t}\n\tconst packageDir = getPackageDir();\n\tconst srcOrDist = existsSync(join(packageDir, \"src\")) ? \"src\" : \"dist\";\n\treturn join(packageDir, srcOrDist, \"core\", \"export-html\");\n}\n\n/** Get path to package.json */\nexport function getPackageJsonPath(): string {\n\treturn join(getPackageDir(), \"package.json\");\n}\n\n/** Get path to README.md */\nexport function getReadmePath(): string {\n\treturn resolve(join(getPackageDir(), \"README.md\"));\n}\n\n/** Get path to docs directory */\nexport function getDocsPath(): string {\n\treturn resolve(join(getPackageDir(), \"docs\"));\n}\n\n/** Get path to examples directory */\nexport function getExamplesPath(): string {\n\treturn resolve(join(getPackageDir(), \"examples\"));\n}\n\n/** Get path to CHANGELOG.md */\nexport function getChangelogPath(): string {\n\treturn resolve(join(getPackageDir(), \"CHANGELOG.md\"));\n}\n\n// =============================================================================\n// App Config (from package.json piConfig)\n// =============================================================================\n\nconst pkg = JSON.parse(readFileSync(getPackageJsonPath(), \"utf-8\"));\n\nexport const APP_NAME: string = pkg.piConfig?.name || \"pi\";\nexport const CONFIG_DIR_NAME: string = pkg.piConfig?.configDir || \".pi\";\nexport const VERSION: string = pkg.version;\n\n// e.g., PI_CODING_AGENT_DIR or TAU_CODING_AGENT_DIR\nexport const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_CODING_AGENT_DIR`;\n\nconst DEFAULT_SHARE_VIEWER_URL = \"https://pi.dev/session/\";\n\n/** Get the share viewer URL for a gist ID */\nexport function getShareViewerUrl(gistId: string): string {\n\tconst baseUrl = process.env.PI_SHARE_VIEWER_URL || DEFAULT_SHARE_VIEWER_URL;\n\treturn `${baseUrl}#${gistId}`;\n}\n\n// =============================================================================\n// User Config Paths (~/.pi/agent/*)\n// =============================================================================\n\n/** Get the agent config directory (e.g., ~/.pi/agent/) */\nexport function getAgentDir(): string {\n\tconst envDir = process.env[ENV_AGENT_DIR];\n\tif (envDir) {\n\t\t// Expand tilde to home directory\n\t\tif (envDir === \"~\") return homedir();\n\t\tif (envDir.startsWith(\"~/\")) return homedir() + envDir.slice(1);\n\t\treturn envDir;\n\t}\n\treturn join(homedir(), CONFIG_DIR_NAME, \"agent\");\n}\n\n/** Get path to user's custom themes directory */\nexport function getCustomThemesDir(): string {\n\treturn join(getAgentDir(), \"themes\");\n}\n\n/** Get path to models.json */\nexport function getModelsPath(): string {\n\tr

Command timed out after 20 seconds


/home/sebas/.npm-global/bin/pi
#!/usr/bin/env node
/**
 * CLI entry point for the refactored coding agent.
 * Uses main.ts with AgentSession and new mode modules.
 *
 * Test with: npx tsx src/cli-new.ts [args...]
 */
import { EnvHttpProxyAgent, setGlobalDispatcher } from "undici";
import { APP_NAME } from "./config.js";
import { main } from "./main.js";
process.title = APP_NAME;
process.env.PI_CODING_AGENT = "true";
process.emitWarning = (() => { });
// bodyTimeout/headersTimeout default to 300s in undici; long local-LLM stalls
// (e.g. vLLM buffering a large tool call) exceed that and abort the SSE stream
// with UND_ERR_BODY_TIMEOUT. Disable both — provider SDKs enforce their own
// AbortController-based deadlines via retry.provider.timeoutMs.
setGlobalDispatcher(new EnvHttpProxyAgent({ bodyTimeout: 0, headersTimeout: 0 }));
main(process.argv.slice(2));
//# sourceMappingURL=cli.js.map


**Considering search issues**

I’m facing a situation where my search command has timed out, and the output is strange. It seems the binary I used may have caused grep to match something unexpected in the compiled jsmap. This isn't great because it complicates things! I realize I need to narrow down my search focus specifically within shell files to get better results. It's a bit of a hassle, but I’m committed to figuring this out!

grep -Rni "PI_CODING_AGENT_DIR" /home/sebas/.bashrc /home/sebas/.profile /home/sebas/.config/fish /home/sebas/.local/bin /home/sebas/pi-config/bin 2>/dev/null | sed -n '1,120p'

grep -Rni "/mnt/rpi/pi-config\|/home/sebas/pi-config" /home/sebas/.bashrc /home/sebas/.profile /home/sebas/.config /home/sebas/.local/bin /home/sebas/pi-config/bin 2>/dev/null | sed -n '1,160p'


/home/sebas/.bashrc:116:export PI_CODING_AGENT_DIR=/home/sebas/pi-config
/home/sebas/.bashrc:123:    session_root="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}/sessions"


/home/sebas/.bashrc:116:export PI_CODING_AGENT_DIR=/home/sebas/pi-config
/home/sebas/.profile:26:    PATH="/home/sebas/pi-config/bin:$HOME/.local/bin:$PATH"
/home/sebas/.config/systemd/user/default.target.wants/install-session-memory-dispatcher.service:8:ExecStart=/usr/bin/env python3 /home/sebas/pi-config/scripts/session-memory-dispatcher.py --host 127.0.0.1 --port 17882
/home/sebas/.config/systemd/user/default.target.wants/install-session-memory-dispatcher.service:13:WorkingDirectory=/home/sebas/pi-config
/home/sebas/.config/systemd/user/install-session-memory-dispatcher.service:8:ExecStart=/usr/bin/env python3 /home/sebas/pi-config/scripts/session-memory-dispatcher.py --host 127.0.0.1 --port 17882
/home/sebas/.config/systemd/user/install-session-memory-dispatcher.service:13:WorkingDirectory=/home/sebas/pi-config


grep -Rni '/mnt/rpi/pi-config' /home/sebas 2>/dev/null | sed -n '1,120p'


Found it.

Problem:
- live `pi` process (`pid 10916`) has stale env:
  - `PI_CODING_AGENT_DIR=/mnt/rpi/pi-config`
- that path does not exist now
- real telegram config/lock live here:
  - `/home/sebas/pi-config/telegram.json`
  - `/home/sebas/pi-config/locks.json`

So:
- install/config exists
- but the long-running `pi` session was started with old config path
- that means telegram bridge ownership/config for that session is wrong

Evidence:
- `ps -p 10916 ...` → running `pi`
- `/proc/10916/environ` → `PI_CODING_AGENT_DIR=/mnt/rpi/pi-config`
- `ls /mnt/rpi/pi-config` → no such dir
- `/home/sebas/.bashrc` now exports `PI_CODING_AGENT_DIR=/home/sebas/pi-config`

Fix:
1. restart that `pi` session from a fresh shell
2. run `/telegram-connect` again in the new session

I did not kill/restart it yet. That would disrupt the active TUI session.

If you want, I can:
- just give exact commands
- or kill the stale `pi` process and relaunch cleanly after your confirm.


Estoy dispuesto a empezarlo en una nueva sesión de Pi, eso no es un problema, pero quiero asegurarme de que se va a mantener activa, incluso después de un reboot de la máquina. La idea es tener una gente al que siempre me puedo comunicar a través de Telegram. Y al que pueda escribir Barra New cuando la sesión se vuelve larga o quiero empezar una nueva conversación.


**Considering architectural needs**

I need to provide a concise answer in Spanish while explaining the architecture. I'll consider using tmux or service. I should inspect my skills by looking at tmux documentation since that's often the source of truth. Proposing a setup might involve some changes, but it could be helpful to ask if the user wants to implement it. Instead, inspecting the current setup could give me a concrete view. I'll check SERVICES-ARCHITECTURE and pi-telegram docs for reconnect behavior.

tmux ls 2>/dev/null || true && echo '---' && systemctl --user list-units --type=service --all | grep -Ei 'pi|telegram|tmux' | sed -n '1,120p'


---
name: tmux-pi-job-setup
description: Set up native tmux-backed one-prompt pi jobs with file logs and simple polling
---

# tmux-pi-job-setup

## Use When

- replacing extension-based side-agent orchestration with simpler native Linux tools
- you need one-prompt `pi` runs in an exact existing directory
- you want tmux persistence plus plain file logs

## Workflow

1. Update `/mnt/rpi/pi-config/SERVICES-ARCHITECTURE.md` first if tmux or launcher behavior changes.
2. Keep runtime storage under `/mnt/rpi/pi-jobs` unless there is a strong reason not to.
3. Use or update these scripts in `/mnt/rpi/pi-config/bin/`:
   - `pi-job-start`
   - `pi-job-check`
   - `pi-job-wait`
   - `pi-job-list`
   - `pi-job-stop`
   - `pi-job-runner`
4. Keep the model simple:
   - exact `cwd`
   - one initial prompt
   - `pi --no-extensions -p` inside tmux
   - file-backed metadata and logs
   - polling by the caller, not cron
5. Do not add follow-up key injection unless the user explicitly asks.

## Runtime Layout

```text
/mnt/rpi/pi-jobs/jobs/<job-id>/
  meta.env
  prompt.txt
  output.log
  status
  exit_code
  started_at
  finished_at
```

## Rules

- prefer shell, tmux, and plain files over extra daemons
- no fixed stall timeout policy; leave polling cadence to the caller
- use `pi-job-check` for current state visibility
- use `pi-job-wait` only when blocking is the right choice


ENOENT: no such file or directory, access '/mnt/rpi/pi-config/SERVICES-ARCHITECTURE.md'


---
  agents-database-http.service                 loaded    active   running Agents Database HTTP API
  filter-chain.service                         loaded    active   running PipeWire filter chain daemon
  install-session-memory-dispatcher.service    loaded    active   running Pi session memory dispatcher
● pipewire-media-session.service               not-found inactive dead    pipewire-media-session.service
  pipewire-pulse.service                       loaded    active   running PipeWire PulseAudio
● pipewire-session-manager.service             not-found inactive dead    pipewire-session-manager.service
  pipewire.service                             loaded    active   running PipeWire Multimedia Service


**Inspecting service paths**

I need to check the actual SERVICES path, which could possibly be at either /home/sebas/SERVICES-ARCHITECTURE.md or /home/sebas/pi-config/SERVICES-ARCHITECTURE.md. It seems smart to inspect the pi-config directory first. I’m curious which path holds the actual document, so I’ll start there and see what I can find. Let’s get a closer look at pi-config and determine the right direction for this!

find /home/sebas/pi-config -maxdepth 2 \( -name 'SERVICES-ARCHITECTURE.md' -o -name '*tmux*' -o -name 'pitmux*' -o -name 'piresume*' -o -name 'pi-job-*' \) | sed -n '1,200p'

find /home/sebas/pi-config/bin -maxdepth 1 -type f | sed -n '1,200p'


/home/sebas/pi-config/bin/pi-job-start
/home/sebas/pi-config/bin/pi-job-active-count
/home/sebas/pi-config/bin/pi-job-runner
/home/sebas/pi-config/bin/pi-job-list
/home/sebas/pi-config/bin/pi-job-stop
/home/sebas/pi-config/bin/pi-job-wait
/home/sebas/pi-config/bin/pi-job-check
/home/sebas/pi-config/scripts/pi-job-status-writer.service
/home/sebas/pi-config/scripts/pi-job-status-writer.timer
/home/sebas/pi-config/SERVICES-ARCHITECTURE.md


/home/sebas/pi-config/bin/pi-job-start
/home/sebas/pi-config/bin/laptop-umount
/home/sebas/pi-config/bin/pi-job-active-count
/home/sebas/pi-config/bin/pi-job-runner
/home/sebas/pi-config/bin/laptop-mount
/home/sebas/pi-config/bin/browser-harness
/home/sebas/pi-config/bin/fd
/home/sebas/pi-config/bin/tmp-cleanup
/home/sebas/pi-config/bin/laptop-read
/home/sebas/pi-config/bin/pi-job-list
/home/sebas/pi-config/bin/laptop-run
/home/sebas/pi-config/bin/laptop-rsync
/home/sebas/pi-config/bin/pi-job-stop
/home/sebas/pi-config/bin/laptop-write
/home/sebas/pi-config/bin/pi-job-wait
/home/sebas/pi-config/bin/pi-job-check


# Services Architecture

This file documents the local service shape on `ballbox-first`, with emphasis on terminal persistence for `pi` and tmux.

## Goal

Keep `pi` usable as an interactive TUI while making tmux only a persistence layer.

Desired properties:

- `pi` runs inside tmux.
- Closing a terminal client does not kill `pi`.
- tmux does not become the primary UI.
- Mouse scroll works for history without moving the `pi` cursor.
- Color rendering stays correct inside tmux.

## Current tmux model

### tmux config

`~/.tmux.conf` is intentionally persistence-first:

- `mouse on`
- `history-limit 100000`
- `set-clipboard on`
- custom prefix: `C-\\`
- status bar off
- dangerous interactive defaults unbound
- copy mode uses vi keys
- wheel scroll enters/uses copy mode
- explicit detach/reload bindings only

### Important tmux bindings

- Prefix: `Ctrl-\\`
- Detach: `Ctrl-\\` then `D`
- Reload config: `Ctrl-\\` then `r`
- Scroll/history:
  - wheel up enters copy mode or scrolls within it
  - wheel down scrolls or exits copy mode

### Why tmux stays minimal

tmux is not used for pane/window management here. It is only the durable PTY container for the running `pi` process.

That means:

- no pane splitting
- no tab/session UI workflow
- no mouse-driven tmux navigation
- no session picker dependency for normal `pi` use

## `pitmux`

`~/.bashrc` defines `pitmux()`.

Purpose:

- start `pi` inside a tmux session
- keep the session attachable later
- keep color output correct inside tmux

### Current launch behavior

`pitmux` forces these env values for the `pi` process:

- `TERM=tmux-256color`
- `COLORTERM=truecolor`
- `TERM_PROGRAM=`

This avoids the brown background / palette mismatch that appeared when `pi` ran inside tmux with the host terminal's `TERM_PROGRAM` leaking through.

### Usage

```bash
pitmux
pitmux pi-1
```

If no name is passed, `pitmux` creates a timestamped session name like:

```text
pi-20260421-165900
```

### Launch shape

`pitmux` runs:

```bash
env TERM=tmux-256color COLORTERM=truecolor TERM_PROGRAM= tmux new-session -A -s <session> pi
```

## `pi` resume helper

`~/.bashrc` also defines `piresume()`.

That wrapper is separate from tmux.

Purpose:

- run `pi` normally
- after exit, print the latest session id hint
- help resume with:

```bash
pi --session <id>
```

## Native tmux batch jobs for subagents

For simple one-prompt subagent runs, prefer native tmux job wrappers instead of extension-based side-agent orchestration.

Desired shape:

- exact existing `cwd` is chosen by caller
- one initial prompt only
- `pi` runs non-interactively (`pi --no-extensions -p`) inside tmux
- output is written to a durable log file
- parent orchestrator can poll state, tail logs, wait, or stop the session
- no cron, no background daemon, no extra service layer

### Runtime layout

Use `/mnt/rpi/pi-jobs` as durable runtime storage.

Per job:

```text
/mnt/rpi/pi-jobs/jobs/<job-id>/
  meta.env
  prompt.txt
  output.log
  status
  exit_code
  started_at
  finished_at
```

### Process model

- `pi-job-start` creates the job directory and tmux session.
- tmux runs a small shell wrapper that executes `pi -p` in the requested directory.
- The wrapper writes status files and appends full stdout/stderr to `output.log`.
- `pi-job-check` reports whether the tmux session is still alive and prints recent log lines.
- `pi-job-wait` is a simple poll loop over tmux session liveness and status files.
- `pi-job-stop` can terminate the tmux session and mark the job as stopped.

### Why this path

This keeps the persistence layer native and inspectable:

- tmux for process lifetime
- shell scripts for control
- plain files for logs and metadata

No interactive key injection workflow is assumed beyond the initial prompt.

## Shell / web service history

### Removed

- `code-server` at `/cli`
- `ttyd`
- `dtach`
- `pi-dashboard`

Reason: they did not match the target shape or they introduced too much UI mismatch.

### Current direction

The current preferred path is:

- local terminal or SSH
- `pitmux` for persistence
- tmux only as a PTY container
- `pi` as the interactive TUI

## Operational rules

- Do not use tmux as the main UI.
- Do not depend on `Ctrl+D` as a detach action; it exits the shell and can kill the session.
- Use tmux detach explicitly.
- Keep browser/web exposure out of the loop unless a future tool actually needs it.

## Open questions

- Whether `pi` can eventually run with a better PTY/session wrapper than tmux.
- Whether a future tool can expose the same persistent TUI model without tmux.

## Relevant files

- `~/.bashrc`
- `~/.tmux.conf`
- `/mnt/rpi/pi-config/AGENTS.md`