@hasna/oldpal 0.1.9 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -20450,12 +20450,12 @@ var exports_anthropic = {};
20450
20450
  __export(exports_anthropic, {
20451
20451
  AnthropicClient: () => AnthropicClient
20452
20452
  });
20453
- import { readFileSync as readFileSync2, existsSync as existsSync4 } from "fs";
20454
- import { homedir as homedir5 } from "os";
20455
- import { join as join6 } from "path";
20453
+ import { readFileSync as readFileSync2, existsSync as existsSync5 } from "fs";
20454
+ import { homedir as homedir6 } from "os";
20455
+ import { join as join7 } from "path";
20456
20456
  function loadApiKeyFromSecrets() {
20457
- const secretsPath = join6(homedir5(), ".secrets");
20458
- if (existsSync4(secretsPath)) {
20457
+ const secretsPath = join7(homedir6(), ".secrets");
20458
+ if (existsSync5(secretsPath)) {
20459
20459
  try {
20460
20460
  const content = readFileSync2(secretsPath, "utf-8");
20461
20461
  const match = content.match(/export\s+ANTHROPIC_API_KEY\s*=\s*["']?([^"'\n]+)["']?/);
@@ -20543,6 +20543,7 @@ class AnthropicClient {
20543
20543
  }
20544
20544
  convertMessages(messages) {
20545
20545
  const result = [];
20546
+ const pendingToolUseIds = new Set;
20546
20547
  for (const msg of messages) {
20547
20548
  if (msg.role === "system")
20548
20549
  continue;
@@ -20558,16 +20559,20 @@ class AnthropicClient {
20558
20559
  name: toolCall.name,
20559
20560
  input: toolCall.input
20560
20561
  });
20562
+ pendingToolUseIds.add(toolCall.id);
20561
20563
  }
20562
20564
  }
20563
20565
  if (msg.toolResults) {
20564
20566
  for (const toolResult of msg.toolResults) {
20565
- content.push({
20566
- type: "tool_result",
20567
- tool_use_id: toolResult.toolCallId,
20568
- content: toolResult.content,
20569
- is_error: toolResult.isError
20570
- });
20567
+ if (pendingToolUseIds.has(toolResult.toolCallId)) {
20568
+ content.push({
20569
+ type: "tool_result",
20570
+ tool_use_id: toolResult.toolCallId,
20571
+ content: toolResult.content,
20572
+ is_error: toolResult.isError
20573
+ });
20574
+ pendingToolUseIds.delete(toolResult.toolCallId);
20575
+ }
20571
20576
  }
20572
20577
  }
20573
20578
  if (content.length > 0) {
@@ -28589,7 +28594,7 @@ var import_react20 = __toESM(require_react(), 1);
28589
28594
  // node_modules/.pnpm/ink@5.2.1_@types+react@18.3.27_react-devtools-core@4.28.5_react@18.3.1/node_modules/ink/build/hooks/use-focus-manager.js
28590
28595
  var import_react21 = __toESM(require_react(), 1);
28591
28596
  // packages/terminal/src/components/App.tsx
28592
- var import_react25 = __toESM(require_react(), 1);
28597
+ var import_react27 = __toESM(require_react(), 1);
28593
28598
  // packages/shared/src/utils.ts
28594
28599
  import { randomUUID } from "crypto";
28595
28600
  function generateId() {
@@ -28702,11 +28707,25 @@ class AgentContext {
28702
28707
  this.messages = [];
28703
28708
  }
28704
28709
  prune() {
28705
- if (this.messages.length > this.maxMessages) {
28706
- const systemMessages = this.messages.filter((m) => m.role === "system");
28707
- const recentMessages = this.messages.filter((m) => m.role !== "system").slice(-(this.maxMessages - systemMessages.length));
28708
- this.messages = [...systemMessages, ...recentMessages];
28710
+ if (this.messages.length <= this.maxMessages) {
28711
+ return;
28709
28712
  }
28713
+ const systemMessages = this.messages.filter((m) => m.role === "system");
28714
+ const nonSystemMessages = this.messages.filter((m) => m.role !== "system");
28715
+ const targetCount = this.maxMessages - systemMessages.length;
28716
+ let recentMessages = nonSystemMessages.slice(-targetCount);
28717
+ while (recentMessages.length > 0 && recentMessages[0].toolResults) {
28718
+ const firstIndex = nonSystemMessages.indexOf(recentMessages[0]);
28719
+ if (firstIndex > 0) {
28720
+ recentMessages = nonSystemMessages.slice(firstIndex - 1);
28721
+ if (recentMessages.length > targetCount + 1) {
28722
+ recentMessages = recentMessages.slice(-(targetCount + 1));
28723
+ }
28724
+ } else {
28725
+ recentMessages = recentMessages.slice(1);
28726
+ }
28727
+ }
28728
+ this.messages = [...systemMessages, ...recentMessages];
28710
28729
  }
28711
28730
  export() {
28712
28731
  return this.messages;
@@ -28770,7 +28789,6 @@ import { join } from "path";
28770
28789
  class ConnectorBridge {
28771
28790
  connectors = new Map;
28772
28791
  async discover(connectorNames) {
28773
- const discovered = [];
28774
28792
  const names = connectorNames || [
28775
28793
  "notion",
28776
28794
  "googledrive",
@@ -28787,19 +28805,22 @@ class ConnectorBridge {
28787
28805
  "openai",
28788
28806
  "elevenlabs"
28789
28807
  ];
28790
- for (const name of names) {
28808
+ const results = await Promise.all(names.map(async (name) => {
28791
28809
  const cli = `connect-${name}`;
28792
28810
  try {
28793
- const result = await Bun.$`which ${cli}`.quiet();
28811
+ const result = await Bun.$`which ${cli}`.quiet().nothrow();
28794
28812
  if (result.exitCode !== 0)
28795
- continue;
28813
+ return null;
28796
28814
  } catch {
28797
- continue;
28815
+ return null;
28798
28816
  }
28799
- const connector = await this.discoverConnector(name, cli);
28817
+ return this.discoverConnector(name, cli);
28818
+ }));
28819
+ const discovered = [];
28820
+ for (const connector of results) {
28800
28821
  if (connector) {
28801
28822
  discovered.push(connector);
28802
- this.connectors.set(name, connector);
28823
+ this.connectors.set(connector.name, connector);
28803
28824
  }
28804
28825
  }
28805
28826
  return discovered;
@@ -29368,35 +29389,260 @@ function parseDuckDuckGoResults(html, maxResults) {
29368
29389
  return results;
29369
29390
  }
29370
29391
 
29392
+ class CurlTool {
29393
+ static tool = {
29394
+ name: "curl",
29395
+ description: "Fetch content from a URL (like curl). Returns text content from web pages, JSON from APIs, etc.",
29396
+ parameters: {
29397
+ type: "object",
29398
+ properties: {
29399
+ url: {
29400
+ type: "string",
29401
+ description: "The URL to fetch"
29402
+ },
29403
+ method: {
29404
+ type: "string",
29405
+ description: "HTTP method (GET, POST, PUT, DELETE). Defaults to GET.",
29406
+ enum: ["GET", "POST", "PUT", "DELETE"],
29407
+ default: "GET"
29408
+ },
29409
+ headers: {
29410
+ type: "object",
29411
+ description: "Optional headers to send with the request"
29412
+ },
29413
+ body: {
29414
+ type: "string",
29415
+ description: "Request body for POST/PUT requests"
29416
+ }
29417
+ },
29418
+ required: ["url"]
29419
+ }
29420
+ };
29421
+ static executor = async (input) => {
29422
+ const url = input.url;
29423
+ const method = input.method || "GET";
29424
+ const headers = input.headers || {};
29425
+ const body = input.body;
29426
+ const timeout = 30000;
29427
+ try {
29428
+ const parsedUrl = new URL(url);
29429
+ const hostname = parsedUrl.hostname;
29430
+ if (hostname === "localhost" || hostname === "127.0.0.1" || hostname.startsWith("192.168.") || hostname.startsWith("10.") || hostname.startsWith("172.")) {
29431
+ return "Error: Cannot fetch from local/private network addresses for security reasons";
29432
+ }
29433
+ const controller = new AbortController;
29434
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
29435
+ const response = await fetch(url, {
29436
+ method,
29437
+ signal: controller.signal,
29438
+ headers: {
29439
+ "User-Agent": "oldpal/1.0 (AI Assistant)",
29440
+ ...headers
29441
+ },
29442
+ body: body && ["POST", "PUT"].includes(method) ? body : undefined
29443
+ });
29444
+ clearTimeout(timeoutId);
29445
+ const contentType = response.headers.get("content-type") || "";
29446
+ let responseBody;
29447
+ if (contentType.includes("application/json")) {
29448
+ try {
29449
+ const json = await response.json();
29450
+ responseBody = JSON.stringify(json, null, 2);
29451
+ } catch {
29452
+ responseBody = await response.text();
29453
+ }
29454
+ } else {
29455
+ responseBody = await response.text();
29456
+ if (contentType.includes("text/html")) {
29457
+ responseBody = extractReadableText(responseBody);
29458
+ }
29459
+ }
29460
+ const maxLength = 30000;
29461
+ if (responseBody.length > maxLength) {
29462
+ responseBody = responseBody.slice(0, maxLength) + `
29463
+
29464
+ [Content truncated...]`;
29465
+ }
29466
+ const statusLine = `HTTP ${response.status} ${response.statusText}`;
29467
+ return `${statusLine}
29468
+
29469
+ ${responseBody || "(empty response)"}`;
29470
+ } catch (error) {
29471
+ if (error instanceof Error) {
29472
+ if (error.name === "AbortError") {
29473
+ return `Error: Request timed out after ${timeout}ms`;
29474
+ }
29475
+ return `Error: ${error.message}`;
29476
+ }
29477
+ return `Error: ${String(error)}`;
29478
+ }
29479
+ };
29480
+ }
29481
+
29371
29482
  class WebTools {
29372
29483
  static registerAll(registry) {
29373
29484
  registry.register(WebFetchTool.tool, WebFetchTool.executor);
29374
29485
  registry.register(WebSearchTool.tool, WebSearchTool.executor);
29486
+ registry.register(CurlTool.tool, CurlTool.executor);
29375
29487
  }
29376
29488
  }
29377
29489
 
29378
- // packages/core/src/skills/loader.ts
29490
+ // packages/core/src/tools/image.ts
29491
+ import { existsSync as existsSync2, writeFileSync, unlinkSync } from "fs";
29492
+ import { tmpdir } from "os";
29379
29493
  import { join as join3 } from "path";
29380
29494
  import { homedir as homedir2 } from "os";
29495
+ async function getViuPath() {
29496
+ const locations = [
29497
+ "viu",
29498
+ join3(homedir2(), ".cargo", "bin", "viu"),
29499
+ "/usr/local/bin/viu",
29500
+ "/opt/homebrew/bin/viu"
29501
+ ];
29502
+ for (const path of locations) {
29503
+ try {
29504
+ const result = await Bun.$`${path} --version`.quiet().nothrow();
29505
+ if (result.exitCode === 0) {
29506
+ return path;
29507
+ }
29508
+ } catch {
29509
+ continue;
29510
+ }
29511
+ }
29512
+ return null;
29513
+ }
29514
+
29515
+ class ImageDisplayTool {
29516
+ static tool = {
29517
+ name: "display_image",
29518
+ description: "Display an image in the terminal. Works with local files and URLs. Supports PNG, JPG, GIF, BMP, and other common formats.",
29519
+ parameters: {
29520
+ type: "object",
29521
+ properties: {
29522
+ path: {
29523
+ type: "string",
29524
+ description: "Path to the image file or URL to fetch"
29525
+ },
29526
+ width: {
29527
+ type: "number",
29528
+ description: "Width in characters (optional, defaults to terminal width)"
29529
+ },
29530
+ height: {
29531
+ type: "number",
29532
+ description: "Height in characters (optional)"
29533
+ }
29534
+ },
29535
+ required: ["path"]
29536
+ }
29537
+ };
29538
+ static executor = async (input) => {
29539
+ const imagePath = input.path;
29540
+ const width = input.width;
29541
+ const height = input.height;
29542
+ const viuPath = await getViuPath();
29543
+ if (!viuPath) {
29544
+ return "Error: viu is not installed. Install with: cargo install viu";
29545
+ }
29546
+ let localPath = imagePath;
29547
+ let tempFile = null;
29548
+ if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
29549
+ try {
29550
+ const response = await fetch(imagePath);
29551
+ if (!response.ok) {
29552
+ return `Error: Failed to fetch image: HTTP ${response.status}`;
29553
+ }
29554
+ const contentType = response.headers.get("content-type") || "";
29555
+ if (!contentType.startsWith("image/")) {
29556
+ return `Error: URL does not point to an image (content-type: ${contentType})`;
29557
+ }
29558
+ const buffer = await response.arrayBuffer();
29559
+ const ext = contentType.split("/")[1]?.split(";")[0] || "png";
29560
+ tempFile = join3(tmpdir(), `oldpal-image-${generateId()}.${ext}`);
29561
+ writeFileSync(tempFile, Buffer.from(buffer));
29562
+ localPath = tempFile;
29563
+ } catch (error) {
29564
+ return `Error: Failed to fetch image: ${error instanceof Error ? error.message : String(error)}`;
29565
+ }
29566
+ }
29567
+ if (!existsSync2(localPath)) {
29568
+ return `Error: Image file not found: ${localPath}`;
29569
+ }
29570
+ try {
29571
+ const args = [];
29572
+ if (width) {
29573
+ args.push("-w", String(width));
29574
+ }
29575
+ if (height) {
29576
+ args.push("-h", String(height));
29577
+ }
29578
+ args.push(localPath);
29579
+ const result = await Bun.$`${viuPath} ${args}`.quiet().nothrow();
29580
+ if (result.exitCode !== 0) {
29581
+ const stderr = result.stderr.toString().trim();
29582
+ return `Error displaying image: ${stderr || "Unknown error"}`;
29583
+ }
29584
+ const output = result.stdout.toString();
29585
+ if (output) {
29586
+ process.stdout.write(output);
29587
+ }
29588
+ return `Image displayed: ${imagePath}${width ? ` (width: ${width})` : ""}${height ? ` (height: ${height})` : ""}`;
29589
+ } catch (error) {
29590
+ return `Error: ${error instanceof Error ? error.message : String(error)}`;
29591
+ } finally {
29592
+ if (tempFile && existsSync2(tempFile)) {
29593
+ try {
29594
+ unlinkSync(tempFile);
29595
+ } catch {}
29596
+ }
29597
+ }
29598
+ };
29599
+ }
29600
+
29601
+ class ImageTools {
29602
+ static registerAll(registry) {
29603
+ registry.register(ImageDisplayTool.tool, ImageDisplayTool.executor);
29604
+ }
29605
+ }
29606
+
29607
+ // packages/core/src/skills/loader.ts
29608
+ import { join as join4 } from "path";
29609
+ import { homedir as homedir3 } from "os";
29381
29610
  var {Glob: Glob2 } = globalThis.Bun;
29382
29611
  class SkillLoader {
29383
29612
  skills = new Map;
29384
29613
  async loadAll(projectDir = process.cwd()) {
29385
- const userSkillsDir = join3(homedir2(), ".oldpal", "skills");
29614
+ const userSkillsDir = join4(homedir3(), ".oldpal", "skills");
29386
29615
  await this.loadFromDirectory(userSkillsDir);
29387
- const projectSkillsDir = join3(projectDir, ".oldpal", "skills");
29616
+ const projectSkillsDir = join4(projectDir, ".oldpal", "skills");
29388
29617
  await this.loadFromDirectory(projectSkillsDir);
29389
29618
  const nestedGlob = new Glob2("**/.oldpal/skills/*/SKILL.md");
29390
29619
  for await (const file of nestedGlob.scan({ cwd: projectDir })) {
29391
- await this.loadSkillFile(join3(projectDir, file));
29620
+ await this.loadSkillFile(join4(projectDir, file));
29392
29621
  }
29393
29622
  }
29394
29623
  async loadFromDirectory(dir) {
29395
29624
  try {
29396
- const glob = new Glob2("*/SKILL.md");
29397
- for await (const file of glob.scan({ cwd: dir })) {
29398
- await this.loadSkillFile(join3(dir, file));
29625
+ const { stat } = await import("fs/promises");
29626
+ try {
29627
+ const stats = await stat(dir);
29628
+ if (!stats.isDirectory())
29629
+ return;
29630
+ } catch {
29631
+ return;
29632
+ }
29633
+ const filesToLoad = [];
29634
+ const skillPrefixGlob = new Glob2("skill-*/SKILL.md");
29635
+ for await (const file of skillPrefixGlob.scan({ cwd: dir })) {
29636
+ filesToLoad.push(join4(dir, file));
29637
+ }
29638
+ const regularGlob = new Glob2("*/SKILL.md");
29639
+ for await (const file of regularGlob.scan({ cwd: dir })) {
29640
+ const dirName = file.split("/")[0];
29641
+ if (!dirName.startsWith("skill-")) {
29642
+ filesToLoad.push(join4(dir, file));
29643
+ }
29399
29644
  }
29645
+ await Promise.all(filesToLoad.map((file) => this.loadSkillFile(file)));
29400
29646
  } catch {}
29401
29647
  }
29402
29648
  async loadSkillFile(filePath) {
@@ -29457,8 +29703,9 @@ class SkillLoader {
29457
29703
  }
29458
29704
 
29459
29705
  // packages/core/src/skills/executor.ts
29706
+ import { dirname as dirname2 } from "path";
29460
29707
  class SkillExecutor {
29461
- prepare(skill, args) {
29708
+ async prepare(skill, args) {
29462
29709
  let content = skill.content;
29463
29710
  content = substituteVariables(content, args);
29464
29711
  if (!skill.content.includes("$ARGUMENTS") && args.length > 0) {
@@ -29466,15 +29713,28 @@ class SkillExecutor {
29466
29713
 
29467
29714
  ARGUMENTS: ${args.join(" ")}`;
29468
29715
  }
29469
- content = this.executeDynamicContext(content);
29716
+ content = await this.executeDynamicContext(content, skill.filePath);
29470
29717
  return content;
29471
29718
  }
29472
- executeDynamicContext(content) {
29473
- const backtickPattern = /`!([^`]+)`/g;
29719
+ async executeDynamicContext(content, skillFilePath) {
29720
+ const backtickPattern = /!\`([^`]+)\`/g;
29721
+ const matches = [...content.matchAll(backtickPattern)];
29722
+ if (matches.length === 0) {
29723
+ return content;
29724
+ }
29725
+ const skillDir = dirname2(skillFilePath);
29474
29726
  let result = content;
29475
- result = result.replace(backtickPattern, (match, command) => {
29476
- return `[Dynamic context: ${command}]`;
29477
- });
29727
+ for (const match of matches) {
29728
+ const fullMatch = match[0];
29729
+ const command = match[1];
29730
+ try {
29731
+ const output = await Bun.$`cd ${skillDir} && ${command}`.quiet().text();
29732
+ result = result.replace(fullMatch, output.trim());
29733
+ } catch (error) {
29734
+ const errorMsg = error instanceof Error ? error.message : String(error);
29735
+ result = result.replace(fullMatch, `[Command failed: ${errorMsg}]`);
29736
+ }
29737
+ }
29478
29738
  return result;
29479
29739
  }
29480
29740
  shouldAutoInvoke(skill, userMessage) {
@@ -29644,9 +29904,9 @@ class HookExecutor {
29644
29904
  }
29645
29905
  }
29646
29906
  // packages/core/src/commands/loader.ts
29647
- import { existsSync as existsSync2, readdirSync, statSync } from "fs";
29648
- import { join as join4, basename, extname } from "path";
29649
- import { homedir as homedir3 } from "os";
29907
+ import { existsSync as existsSync3, readdirSync, statSync } from "fs";
29908
+ import { join as join5, basename, extname } from "path";
29909
+ import { homedir as homedir4 } from "os";
29650
29910
 
29651
29911
  class CommandLoader {
29652
29912
  commands = new Map;
@@ -29656,17 +29916,17 @@ class CommandLoader {
29656
29916
  }
29657
29917
  async loadAll() {
29658
29918
  this.commands.clear();
29659
- const globalDir = join4(homedir3(), ".oldpal", "commands");
29919
+ const globalDir = join5(homedir4(), ".oldpal", "commands");
29660
29920
  await this.loadFromDirectory(globalDir, "global");
29661
- const projectDir = join4(this.cwd, ".oldpal", "commands");
29921
+ const projectDir = join5(this.cwd, ".oldpal", "commands");
29662
29922
  await this.loadFromDirectory(projectDir, "project");
29663
29923
  }
29664
29924
  async loadFromDirectory(dir, source, prefix = "") {
29665
- if (!existsSync2(dir))
29925
+ if (!existsSync3(dir))
29666
29926
  return;
29667
29927
  const entries = readdirSync(dir);
29668
29928
  for (const entry of entries) {
29669
- const fullPath = join4(dir, entry);
29929
+ const fullPath = join5(dir, entry);
29670
29930
  const stat = statSync(fullPath);
29671
29931
  if (stat.isDirectory()) {
29672
29932
  const newPrefix = prefix ? `${prefix}:${entry}` : entry;
@@ -29844,9 +30104,9 @@ ${stderr}`;
29844
30104
  }
29845
30105
  }
29846
30106
  // packages/core/src/commands/builtin.ts
29847
- import { join as join5 } from "path";
29848
- import { homedir as homedir4 } from "os";
29849
- import { existsSync as existsSync3, mkdirSync, writeFileSync } from "fs";
30107
+ import { join as join6 } from "path";
30108
+ import { homedir as homedir5 } from "os";
30109
+ import { existsSync as existsSync4, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
29850
30110
 
29851
30111
  class BuiltinCommands {
29852
30112
  tokenUsage = {
@@ -30016,8 +30276,8 @@ Format the summary as a brief bullet-point list. This summary will replace the c
30016
30276
  content: "",
30017
30277
  handler: async (args, context) => {
30018
30278
  const configPaths = [
30019
- join5(context.cwd, ".oldpal", "config.json"),
30020
- join5(homedir4(), ".oldpal", "config.json")
30279
+ join6(context.cwd, ".oldpal", "config.json"),
30280
+ join6(homedir5(), ".oldpal", "config.json")
30021
30281
  ];
30022
30282
  let message = `
30023
30283
  **Configuration**
@@ -30026,16 +30286,16 @@ Format the summary as a brief bullet-point list. This summary will replace the c
30026
30286
  message += `**Config File Locations:**
30027
30287
  `;
30028
30288
  for (const path of configPaths) {
30029
- const exists = existsSync3(path);
30289
+ const exists = existsSync4(path);
30030
30290
  message += ` ${exists ? "\u2713" : "\u25CB"} ${path}
30031
30291
  `;
30032
30292
  }
30033
30293
  message += `
30034
30294
  **Commands Directories:**
30035
30295
  `;
30036
- message += ` - Project: ${join5(context.cwd, ".oldpal", "commands")}
30296
+ message += ` - Project: ${join6(context.cwd, ".oldpal", "commands")}
30037
30297
  `;
30038
- message += ` - Global: ${join5(homedir4(), ".oldpal", "commands")}
30298
+ message += ` - Global: ${join6(homedir5(), ".oldpal", "commands")}
30039
30299
  `;
30040
30300
  context.emit("text", message);
30041
30301
  context.emit("done");
@@ -30051,7 +30311,7 @@ Format the summary as a brief bullet-point list. This summary will replace the c
30051
30311
  selfHandled: true,
30052
30312
  content: "",
30053
30313
  handler: async (args, context) => {
30054
- const commandsDir = join5(context.cwd, ".oldpal", "commands");
30314
+ const commandsDir = join6(context.cwd, ".oldpal", "commands");
30055
30315
  mkdirSync(commandsDir, { recursive: true });
30056
30316
  const exampleCommand = `---
30057
30317
  name: review
@@ -30080,9 +30340,9 @@ Please review the current code changes and provide feedback on:
30080
30340
 
30081
30341
  If there are staged git changes, focus on those. Otherwise, ask what code to review.
30082
30342
  `;
30083
- const examplePath = join5(commandsDir, "review.md");
30084
- if (!existsSync3(examplePath)) {
30085
- writeFileSync(examplePath, exampleCommand);
30343
+ const examplePath = join6(commandsDir, "review.md");
30344
+ if (!existsSync4(examplePath)) {
30345
+ writeFileSync2(examplePath, exampleCommand);
30086
30346
  }
30087
30347
  let message = `
30088
30348
  **Initialized oldpal**
@@ -30256,8 +30516,8 @@ async function createLLMClient(config) {
30256
30516
  }
30257
30517
 
30258
30518
  // packages/core/src/config.ts
30259
- import { join as join7 } from "path";
30260
- import { homedir as homedir6 } from "os";
30519
+ import { join as join8 } from "path";
30520
+ import { homedir as homedir7 } from "os";
30261
30521
  var DEFAULT_CONFIG = {
30262
30522
  llm: {
30263
30523
  provider: "anthropic",
@@ -30287,13 +30547,13 @@ var DEFAULT_CONFIG = {
30287
30547
  ]
30288
30548
  };
30289
30549
  function getConfigDir() {
30290
- return join7(homedir6(), ".oldpal");
30550
+ return join8(homedir7(), ".oldpal");
30291
30551
  }
30292
30552
  function getConfigPath(filename) {
30293
- return join7(getConfigDir(), filename);
30553
+ return join8(getConfigDir(), filename);
30294
30554
  }
30295
30555
  function getProjectConfigDir(cwd2 = process.cwd()) {
30296
- return join7(cwd2, ".oldpal");
30556
+ return join8(cwd2, ".oldpal");
30297
30557
  }
30298
30558
  async function loadConfig(cwd2 = process.cwd()) {
30299
30559
  const config = { ...DEFAULT_CONFIG };
@@ -30306,7 +30566,7 @@ async function loadConfig(cwd2 = process.cwd()) {
30306
30566
  if (userConfig.voice)
30307
30567
  config.voice = { ...config.voice, ...userConfig.voice };
30308
30568
  }
30309
- const projectConfigPath = join7(getProjectConfigDir(cwd2), "settings.json");
30569
+ const projectConfigPath = join8(getProjectConfigDir(cwd2), "settings.json");
30310
30570
  const projectConfig = await loadJsonFile(projectConfigPath);
30311
30571
  if (projectConfig) {
30312
30572
  Object.assign(config, projectConfig);
@@ -30315,7 +30575,7 @@ async function loadConfig(cwd2 = process.cwd()) {
30315
30575
  if (projectConfig.voice)
30316
30576
  config.voice = { ...config.voice, ...projectConfig.voice };
30317
30577
  }
30318
- const localConfigPath = join7(getProjectConfigDir(cwd2), "settings.local.json");
30578
+ const localConfigPath = join8(getProjectConfigDir(cwd2), "settings.local.json");
30319
30579
  const localConfig = await loadJsonFile(localConfigPath);
30320
30580
  if (localConfig) {
30321
30581
  Object.assign(config, localConfig);
@@ -30333,7 +30593,7 @@ async function loadHooksConfig(cwd2 = process.cwd()) {
30333
30593
  if (userHooks?.hooks) {
30334
30594
  mergeHooks(hooks, userHooks.hooks);
30335
30595
  }
30336
- const projectHooksPath = join7(getProjectConfigDir(cwd2), "hooks.json");
30596
+ const projectHooksPath = join8(getProjectConfigDir(cwd2), "hooks.json");
30337
30597
  const projectHooks = await loadJsonFile(projectHooksPath);
30338
30598
  if (projectHooks?.hooks) {
30339
30599
  mergeHooks(hooks, projectHooks.hooks);
@@ -30359,6 +30619,47 @@ async function loadJsonFile(path) {
30359
30619
  return null;
30360
30620
  }
30361
30621
  }
30622
+ async function ensureConfigDir() {
30623
+ const { mkdir } = await import("fs/promises");
30624
+ const configDir = getConfigDir();
30625
+ await Promise.all([
30626
+ mkdir(configDir, { recursive: true }),
30627
+ mkdir(join8(configDir, "sessions"), { recursive: true }),
30628
+ mkdir(join8(configDir, "skills"), { recursive: true })
30629
+ ]);
30630
+ }
30631
+ async function loadSystemPrompt(cwd2 = process.cwd()) {
30632
+ const prompts = [];
30633
+ const globalPromptPath = getConfigPath("OLDPAL.md");
30634
+ const globalPrompt = await loadTextFile(globalPromptPath);
30635
+ if (globalPrompt) {
30636
+ prompts.push(globalPrompt);
30637
+ }
30638
+ const projectPromptPath = join8(getProjectConfigDir(cwd2), "OLDPAL.md");
30639
+ const projectPrompt = await loadTextFile(projectPromptPath);
30640
+ if (projectPrompt) {
30641
+ prompts.push(projectPrompt);
30642
+ }
30643
+ if (prompts.length === 0) {
30644
+ return null;
30645
+ }
30646
+ return prompts.join(`
30647
+
30648
+ ---
30649
+
30650
+ `);
30651
+ }
30652
+ async function loadTextFile(path) {
30653
+ try {
30654
+ const file = Bun.file(path);
30655
+ if (!await file.exists()) {
30656
+ return null;
30657
+ }
30658
+ return await file.text();
30659
+ } catch {
30660
+ return null;
30661
+ }
30662
+ }
30362
30663
 
30363
30664
  // packages/core/src/agent/loop.ts
30364
30665
  class AgentLoop {
@@ -30401,18 +30702,31 @@ class AgentLoop {
30401
30702
  this.onTokenUsage = options.onTokenUsage;
30402
30703
  }
30403
30704
  async initialize() {
30404
- this.config = await loadConfig(this.cwd);
30405
- this.llmClient = await createLLMClient(this.config.llm);
30705
+ const [config] = await Promise.all([
30706
+ loadConfig(this.cwd),
30707
+ ensureConfigDir()
30708
+ ]);
30709
+ this.config = config;
30710
+ const [, , , hooksConfig, systemPrompt] = await Promise.all([
30711
+ createLLMClient(this.config.llm).then((client) => {
30712
+ this.llmClient = client;
30713
+ }),
30714
+ this.connectorBridge.discover(this.config.connectors),
30715
+ this.skillLoader.loadAll(this.cwd),
30716
+ loadHooksConfig(this.cwd),
30717
+ loadSystemPrompt(this.cwd),
30718
+ this.commandLoader.loadAll()
30719
+ ]);
30406
30720
  this.toolRegistry.register(BashTool.tool, BashTool.executor);
30407
30721
  FilesystemTools.registerAll(this.toolRegistry);
30408
30722
  WebTools.registerAll(this.toolRegistry);
30409
- await this.connectorBridge.discover(this.config.connectors);
30723
+ ImageTools.registerAll(this.toolRegistry);
30410
30724
  this.connectorBridge.registerAll(this.toolRegistry);
30411
- await this.skillLoader.loadAll(this.cwd);
30412
- await this.commandLoader.loadAll();
30413
30725
  this.builtinCommands.registerAll(this.commandLoader);
30414
- const hooksConfig = await loadHooksConfig(this.cwd);
30415
30726
  this.hookLoader.load(hooksConfig);
30727
+ if (systemPrompt) {
30728
+ this.context.addSystemMessage(systemPrompt);
30729
+ }
30416
30730
  await this.hookExecutor.execute(this.hookLoader.getHooks("SessionStart"), {
30417
30731
  session_id: this.sessionId,
30418
30732
  hook_event_name: "SessionStart",
@@ -30565,7 +30879,7 @@ class AgentLoop {
30565
30879
  return false;
30566
30880
  }
30567
30881
  const argsList = args ? args.split(/\s+/) : [];
30568
- const content = this.skillExecutor.prepare(skill, argsList);
30882
+ const content = await this.skillExecutor.prepare(skill, argsList);
30569
30883
  this.context.addSystemMessage(content);
30570
30884
  this.context.addUserMessage(`Execute the "${skillName}" skill with arguments: ${args || "(none)"}`);
30571
30885
  await this.runLoop();
@@ -30611,9 +30925,9 @@ class AgentLoop {
30611
30925
  init_anthropic();
30612
30926
 
30613
30927
  // packages/core/src/logger.ts
30614
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, appendFileSync } from "fs";
30615
- import { join as join8 } from "path";
30616
- import { homedir as homedir7 } from "os";
30928
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, appendFileSync } from "fs";
30929
+ import { join as join9 } from "path";
30930
+ import { homedir as homedir8 } from "os";
30617
30931
 
30618
30932
  class Logger {
30619
30933
  logDir;
@@ -30621,13 +30935,13 @@ class Logger {
30621
30935
  sessionId;
30622
30936
  constructor(sessionId) {
30623
30937
  this.sessionId = sessionId;
30624
- this.logDir = join8(homedir7(), ".oldpal", "logs");
30938
+ this.logDir = join9(homedir8(), ".oldpal", "logs");
30625
30939
  this.ensureDir(this.logDir);
30626
30940
  const date = new Date().toISOString().split("T")[0];
30627
- this.logFile = join8(this.logDir, `${date}.log`);
30941
+ this.logFile = join9(this.logDir, `${date}.log`);
30628
30942
  }
30629
30943
  ensureDir(dir) {
30630
- if (!existsSync5(dir)) {
30944
+ if (!existsSync6(dir)) {
30631
30945
  mkdirSync2(dir, { recursive: true });
30632
30946
  }
30633
30947
  }
@@ -30667,12 +30981,12 @@ class SessionStorage {
30667
30981
  sessionId;
30668
30982
  constructor(sessionId) {
30669
30983
  this.sessionId = sessionId;
30670
- this.sessionsDir = join8(homedir7(), ".oldpal", "sessions");
30984
+ this.sessionsDir = join9(homedir8(), ".oldpal", "sessions");
30671
30985
  this.ensureDir(this.sessionsDir);
30672
- this.sessionFile = join8(this.sessionsDir, `${sessionId}.json`);
30986
+ this.sessionFile = join9(this.sessionsDir, `${sessionId}.json`);
30673
30987
  }
30674
30988
  ensureDir(dir) {
30675
- if (!existsSync5(dir)) {
30989
+ if (!existsSync6(dir)) {
30676
30990
  mkdirSync2(dir, { recursive: true });
30677
30991
  }
30678
30992
  }
@@ -30686,15 +31000,15 @@ class SessionStorage {
30686
31000
  }
30687
31001
  }
30688
31002
  function initOldpalDir() {
30689
- const baseDir = join8(homedir7(), ".oldpal");
31003
+ const baseDir = join9(homedir8(), ".oldpal");
30690
31004
  const dirs = [
30691
31005
  baseDir,
30692
- join8(baseDir, "sessions"),
30693
- join8(baseDir, "logs"),
30694
- join8(baseDir, "skills")
31006
+ join9(baseDir, "sessions"),
31007
+ join9(baseDir, "logs"),
31008
+ join9(baseDir, "skills")
30695
31009
  ];
30696
31010
  for (const dir of dirs) {
30697
- if (!existsSync5(dir)) {
31011
+ if (!existsSync6(dir)) {
30698
31012
  mkdirSync2(dir, { recursive: true });
30699
31013
  }
30700
31014
  }
@@ -31027,8 +31341,18 @@ function parseMarkdown(text) {
31027
31341
 
31028
31342
  // packages/terminal/src/components/Messages.tsx
31029
31343
  var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
31030
- function Messages4({ messages, currentResponse, currentToolCall, lastToolResult, activityLog = [] }) {
31031
- const visibleMessages = messages.slice(-15);
31344
+ function Messages4({
31345
+ messages,
31346
+ currentResponse,
31347
+ currentToolCall,
31348
+ lastToolResult,
31349
+ activityLog = [],
31350
+ scrollOffset = 0,
31351
+ maxVisible = 10
31352
+ }) {
31353
+ const endIndex = messages.length - scrollOffset;
31354
+ const startIndex = Math.max(0, endIndex - maxVisible);
31355
+ const visibleMessages = messages.slice(startIndex, endIndex);
31032
31356
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31033
31357
  flexDirection: "column",
31034
31358
  children: [
@@ -31036,59 +31360,25 @@ function Messages4({ messages, currentResponse, currentToolCall, lastToolResult,
31036
31360
  message
31037
31361
  }, message.id, false, undefined, this)),
31038
31362
  activityLog.map((entry) => {
31039
- if (entry.type === "tool_call" && entry.toolCall) {
31363
+ if (entry.type === "text" && entry.content) {
31040
31364
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31041
31365
  marginY: 1,
31042
31366
  children: [
31043
31367
  /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
31044
31368
  dimColor: true,
31045
- children: "\u25D0 "
31369
+ children: "\u25CF "
31046
31370
  }, undefined, false, undefined, this),
31047
- /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
31048
- dimColor: true,
31049
- children: formatToolCall(entry.toolCall)
31371
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31372
+ flexGrow: 1,
31373
+ children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Markdown, {
31374
+ content: entry.content
31375
+ }, undefined, false, undefined, this)
31050
31376
  }, undefined, false, undefined, this)
31051
31377
  ]
31052
31378
  }, entry.id, true, undefined, this);
31053
31379
  }
31054
- if (entry.type === "tool_result" && entry.toolResult) {
31055
- return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31056
- marginLeft: 2,
31057
- children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
31058
- dimColor: true,
31059
- children: [
31060
- "\u2192 ",
31061
- truncate(entry.toolResult.content, 100)
31062
- ]
31063
- }, undefined, true, undefined, this)
31064
- }, entry.id, false, undefined, this);
31065
- }
31066
31380
  return null;
31067
31381
  }),
31068
- currentToolCall && !activityLog.some((e) => e.toolCall?.id === currentToolCall.id) && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31069
- marginY: 1,
31070
- children: [
31071
- /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
31072
- dimColor: true,
31073
- children: "\u25D0 "
31074
- }, undefined, false, undefined, this),
31075
- /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
31076
- dimColor: true,
31077
- children: formatToolCall(currentToolCall)
31078
- }, undefined, false, undefined, this)
31079
- ]
31080
- }, undefined, true, undefined, this),
31081
- lastToolResult && !activityLog.some((e) => e.toolResult?.toolCallId === lastToolResult.toolCallId) && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31082
- marginY: 1,
31083
- marginLeft: 2,
31084
- children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
31085
- dimColor: true,
31086
- children: [
31087
- "\u2192 ",
31088
- truncate(lastToolResult.content, 100)
31089
- ]
31090
- }, undefined, true, undefined, this)
31091
- }, undefined, false, undefined, this),
31092
31382
  currentResponse && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31093
31383
  marginY: 1,
31094
31384
  children: [
@@ -31127,6 +31417,7 @@ function MessageBubble({ message }) {
31127
31417
  ]
31128
31418
  }, undefined, true, undefined, this);
31129
31419
  }
31420
+ const toolCount = message.toolCalls?.length || 0;
31130
31421
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31131
31422
  marginY: 1,
31132
31423
  flexDirection: "column",
@@ -31145,54 +31436,22 @@ function MessageBubble({ message }) {
31145
31436
  }, undefined, false, undefined, this)
31146
31437
  ]
31147
31438
  }, undefined, true, undefined, this),
31148
- message.toolCalls?.map((toolCall) => /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31439
+ toolCount > 0 && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31149
31440
  marginLeft: 2,
31150
- marginTop: 1,
31151
31441
  children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
31152
31442
  dimColor: true,
31153
31443
  children: [
31154
- "\u25D0 ",
31155
- formatToolCall(toolCall)
31444
+ "[",
31445
+ toolCount,
31446
+ " tool",
31447
+ toolCount > 1 ? "s" : "",
31448
+ " used]"
31156
31449
  ]
31157
31450
  }, undefined, true, undefined, this)
31158
- }, toolCall.id, false, undefined, this))
31451
+ }, undefined, false, undefined, this)
31159
31452
  ]
31160
31453
  }, undefined, true, undefined, this);
31161
31454
  }
31162
- function formatToolCall(toolCall) {
31163
- const { name, input } = toolCall;
31164
- switch (name) {
31165
- case "bash":
31166
- return `Running: ${truncate(String(input.command || ""), 60)}`;
31167
- case "read":
31168
- return `Reading: ${truncate(String(input.path || input.file_path || ""), 60)}`;
31169
- case "write":
31170
- return `Writing: ${truncate(String(input.path || input.file_path || ""), 60)}`;
31171
- case "glob":
31172
- return `Finding: ${truncate(String(input.pattern || ""), 60)}`;
31173
- case "grep":
31174
- return `Searching: ${truncate(String(input.pattern || ""), 60)}`;
31175
- case "notion":
31176
- return `Notion: ${truncate(String(input.command || input.action || ""), 60)}`;
31177
- case "gmail":
31178
- return `Gmail: ${truncate(String(input.command || input.action || ""), 60)}`;
31179
- case "googledrive":
31180
- return `Drive: ${truncate(String(input.command || input.action || ""), 60)}`;
31181
- case "googlecalendar":
31182
- return `Calendar: ${truncate(String(input.command || input.action || ""), 60)}`;
31183
- case "linear":
31184
- return `Linear: ${truncate(String(input.command || input.action || ""), 60)}`;
31185
- case "slack":
31186
- return `Slack: ${truncate(String(input.command || input.action || ""), 60)}`;
31187
- default:
31188
- return `${name}: ${truncate(JSON.stringify(input), 50)}`;
31189
- }
31190
- }
31191
- function truncate(text, maxLength) {
31192
- if (text.length <= maxLength)
31193
- return text;
31194
- return text.slice(0, maxLength - 3) + "...";
31195
- }
31196
31455
 
31197
31456
  // packages/terminal/src/components/Status.tsx
31198
31457
  var jsx_dev_runtime4 = __toESM(require_jsx_dev_runtime(), 1);
@@ -31289,26 +31548,232 @@ function Spinner2({ label }) {
31289
31548
  }, undefined, true, undefined, this);
31290
31549
  }
31291
31550
 
31292
- // packages/terminal/src/components/App.tsx
31551
+ // packages/terminal/src/components/ProcessingIndicator.tsx
31552
+ var import_react25 = __toESM(require_react(), 1);
31293
31553
  var jsx_dev_runtime6 = __toESM(require_jsx_dev_runtime(), 1);
31554
+ function ProcessingIndicator({
31555
+ isProcessing,
31556
+ startTime,
31557
+ tokenCount = 0,
31558
+ isThinking = false
31559
+ }) {
31560
+ const [elapsed, setElapsed] = import_react25.useState(0);
31561
+ import_react25.useEffect(() => {
31562
+ if (!isProcessing || !startTime) {
31563
+ setElapsed(0);
31564
+ return;
31565
+ }
31566
+ const interval = setInterval(() => {
31567
+ setElapsed(Math.floor((Date.now() - startTime) / 1000));
31568
+ }, 1000);
31569
+ setElapsed(Math.floor((Date.now() - startTime) / 1000));
31570
+ return () => clearInterval(interval);
31571
+ }, [isProcessing, startTime]);
31572
+ if (!isProcessing) {
31573
+ return null;
31574
+ }
31575
+ const formatTime = (seconds) => {
31576
+ if (seconds < 60) {
31577
+ return `${seconds}s`;
31578
+ }
31579
+ const mins = Math.floor(seconds / 60);
31580
+ const secs = seconds % 60;
31581
+ return `${mins}m ${secs}s`;
31582
+ };
31583
+ const formatTokens = (tokens) => {
31584
+ if (tokens >= 1000) {
31585
+ return `${(tokens / 1000).toFixed(1)}k`;
31586
+ }
31587
+ return String(tokens);
31588
+ };
31589
+ const parts = [];
31590
+ parts.push("esc to interrupt");
31591
+ if (elapsed > 0) {
31592
+ parts.push(formatTime(elapsed));
31593
+ }
31594
+ if (tokenCount > 0) {
31595
+ parts.push(`\u2193 ${formatTokens(tokenCount)} tokens`);
31596
+ }
31597
+ if (isThinking) {
31598
+ parts.push("thinking");
31599
+ }
31600
+ const label = isThinking ? "Thinking" : "Working";
31601
+ return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
31602
+ marginY: 1,
31603
+ children: [
31604
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
31605
+ dimColor: true,
31606
+ children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(build_default2, {
31607
+ type: "dots"
31608
+ }, undefined, false, undefined, this)
31609
+ }, undefined, false, undefined, this),
31610
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
31611
+ dimColor: true,
31612
+ children: [
31613
+ " ",
31614
+ label,
31615
+ "... "
31616
+ ]
31617
+ }, undefined, true, undefined, this),
31618
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
31619
+ dimColor: true,
31620
+ children: [
31621
+ "(",
31622
+ parts.join(" \xB7 "),
31623
+ ")"
31624
+ ]
31625
+ }, undefined, true, undefined, this)
31626
+ ]
31627
+ }, undefined, true, undefined, this);
31628
+ }
31629
+
31630
+ // packages/terminal/src/components/ToolCallBox.tsx
31631
+ var import_react26 = __toESM(require_react(), 1);
31632
+ var jsx_dev_runtime7 = __toESM(require_jsx_dev_runtime(), 1);
31633
+ function ToolCallBox({
31634
+ entries,
31635
+ maxVisible = 3,
31636
+ isExpanded = false,
31637
+ onToggleExpand
31638
+ }) {
31639
+ if (entries.length === 0) {
31640
+ return null;
31641
+ }
31642
+ const visibleEntries = isExpanded ? entries : entries.slice(-maxVisible);
31643
+ const hiddenCount = entries.length - visibleEntries.length;
31644
+ return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
31645
+ flexDirection: "column",
31646
+ borderStyle: "round",
31647
+ borderColor: "gray",
31648
+ paddingX: 1,
31649
+ marginY: 1,
31650
+ children: [
31651
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
31652
+ justifyContent: "space-between",
31653
+ children: [
31654
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
31655
+ dimColor: true,
31656
+ bold: true,
31657
+ children: [
31658
+ "Tools (",
31659
+ entries.length,
31660
+ ")"
31661
+ ]
31662
+ }, undefined, true, undefined, this),
31663
+ entries.length > maxVisible && /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
31664
+ dimColor: true,
31665
+ children: isExpanded ? "Ctrl+O to collapse" : "Ctrl+O to expand"
31666
+ }, undefined, false, undefined, this)
31667
+ ]
31668
+ }, undefined, true, undefined, this),
31669
+ hiddenCount > 0 && !isExpanded && /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
31670
+ dimColor: true,
31671
+ children: [
31672
+ " +",
31673
+ hiddenCount,
31674
+ " more above..."
31675
+ ]
31676
+ }, undefined, true, undefined, this),
31677
+ visibleEntries.map((entry, index) => /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(ToolCallRow, {
31678
+ entry
31679
+ }, entry.toolCall.id, false, undefined, this))
31680
+ ]
31681
+ }, undefined, true, undefined, this);
31682
+ }
31683
+ function ToolCallRow({ entry }) {
31684
+ const { toolCall, result } = entry;
31685
+ const statusIcon = result ? result.isError ? "\u2717" : "\u2713" : "\u25D0";
31686
+ const statusColor = result ? result.isError ? "red" : "green" : "yellow";
31687
+ return /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Box_default, {
31688
+ children: [
31689
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
31690
+ color: statusColor,
31691
+ children: [
31692
+ statusIcon,
31693
+ " "
31694
+ ]
31695
+ }, undefined, true, undefined, this),
31696
+ /* @__PURE__ */ jsx_dev_runtime7.jsxDEV(Text, {
31697
+ dimColor: true,
31698
+ children: formatToolCall(toolCall)
31699
+ }, undefined, false, undefined, this)
31700
+ ]
31701
+ }, undefined, true, undefined, this);
31702
+ }
31703
+ function formatToolCall(toolCall) {
31704
+ const { name, input } = toolCall;
31705
+ switch (name) {
31706
+ case "bash":
31707
+ return `bash: ${truncate(String(input.command || ""), 50)}`;
31708
+ case "curl":
31709
+ case "web_fetch":
31710
+ return `fetch: ${truncate(String(input.url || ""), 50)}`;
31711
+ case "web_search":
31712
+ return `search: ${truncate(String(input.query || ""), 50)}`;
31713
+ case "read":
31714
+ return `read: ${truncate(String(input.path || input.file_path || ""), 50)}`;
31715
+ case "write":
31716
+ return `write: ${truncate(String(input.path || input.file_path || ""), 50)}`;
31717
+ case "glob":
31718
+ return `glob: ${truncate(String(input.pattern || ""), 50)}`;
31719
+ case "grep":
31720
+ return `grep: ${truncate(String(input.pattern || ""), 50)}`;
31721
+ case "notion":
31722
+ return `notion: ${truncate(String(input.command || input.action || ""), 50)}`;
31723
+ case "gmail":
31724
+ return `gmail: ${truncate(String(input.command || input.action || ""), 50)}`;
31725
+ case "googledrive":
31726
+ return `drive: ${truncate(String(input.command || input.action || ""), 50)}`;
31727
+ case "googlecalendar":
31728
+ return `calendar: ${truncate(String(input.command || input.action || ""), 50)}`;
31729
+ case "linear":
31730
+ return `linear: ${truncate(String(input.command || input.action || ""), 50)}`;
31731
+ case "slack":
31732
+ return `slack: ${truncate(String(input.command || input.action || ""), 50)}`;
31733
+ default:
31734
+ return `${name}: ${truncate(JSON.stringify(input), 40)}`;
31735
+ }
31736
+ }
31737
+ function truncate(text, maxLength) {
31738
+ if (text.length <= maxLength)
31739
+ return text;
31740
+ return text.slice(0, maxLength - 3) + "...";
31741
+ }
31742
+ function useToolCallExpansion() {
31743
+ const [isExpanded, setIsExpanded] = import_react26.useState(false);
31744
+ use_input_default((input, key) => {
31745
+ if (key.ctrl && input === "o") {
31746
+ setIsExpanded((prev) => !prev);
31747
+ }
31748
+ });
31749
+ return { isExpanded, setIsExpanded };
31750
+ }
31751
+
31752
+ // packages/terminal/src/components/App.tsx
31753
+ var jsx_dev_runtime8 = __toESM(require_jsx_dev_runtime(), 1);
31294
31754
  function App2({ cwd: cwd2 }) {
31295
31755
  const { exit } = use_app_default();
31296
- const [client, setClient] = import_react25.useState(null);
31297
- const [messages, setMessages] = import_react25.useState([]);
31298
- const [currentResponse, setCurrentResponse] = import_react25.useState("");
31299
- const [currentToolCall, setCurrentToolCall] = import_react25.useState();
31300
- const [lastToolResult, setLastToolResult] = import_react25.useState();
31301
- const [isProcessing, setIsProcessing] = import_react25.useState(false);
31302
- const [isInitializing, setIsInitializing] = import_react25.useState(true);
31303
- const [error, setError] = import_react25.useState(null);
31304
- const [messageQueue, setMessageQueue] = import_react25.useState([]);
31305
- const [activityLog, setActivityLog] = import_react25.useState([]);
31306
- const [tokenUsage, setTokenUsage] = import_react25.useState();
31307
- const responseRef = import_react25.useRef("");
31308
- const clientRef = import_react25.useRef(null);
31309
- const toolCallsRef = import_react25.useRef([]);
31310
- const toolResultsRef = import_react25.useRef([]);
31311
- const processQueue = import_react25.useCallback(async () => {
31756
+ const [client, setClient] = import_react27.useState(null);
31757
+ const [messages, setMessages] = import_react27.useState([]);
31758
+ const [currentResponse, setCurrentResponse] = import_react27.useState("");
31759
+ const [currentToolCall, setCurrentToolCall] = import_react27.useState();
31760
+ const [lastToolResult, setLastToolResult] = import_react27.useState();
31761
+ const [isProcessing, setIsProcessing] = import_react27.useState(false);
31762
+ const [isInitializing, setIsInitializing] = import_react27.useState(true);
31763
+ const [error, setError] = import_react27.useState(null);
31764
+ const [messageQueue, setMessageQueue] = import_react27.useState([]);
31765
+ const [activityLog, setActivityLog] = import_react27.useState([]);
31766
+ const [tokenUsage, setTokenUsage] = import_react27.useState();
31767
+ const [processingStartTime, setProcessingStartTime] = import_react27.useState();
31768
+ const [currentTurnTokens, setCurrentTurnTokens] = import_react27.useState(0);
31769
+ const [scrollOffset, setScrollOffset] = import_react27.useState(0);
31770
+ const [autoScroll, setAutoScroll] = import_react27.useState(true);
31771
+ const { isExpanded: toolsExpanded } = useToolCallExpansion();
31772
+ const responseRef = import_react27.useRef("");
31773
+ const clientRef = import_react27.useRef(null);
31774
+ const toolCallsRef = import_react27.useRef([]);
31775
+ const toolResultsRef = import_react27.useRef([]);
31776
+ const processQueue = import_react27.useCallback(async () => {
31312
31777
  if (!clientRef.current || messageQueue.length === 0)
31313
31778
  return;
31314
31779
  const nextMessage = messageQueue[0];
@@ -31328,10 +31793,12 @@ function App2({ cwd: cwd2 }) {
31328
31793
  setCurrentToolCall(undefined);
31329
31794
  setLastToolResult(undefined);
31330
31795
  setActivityLog([]);
31796
+ setProcessingStartTime(Date.now());
31797
+ setCurrentTurnTokens(0);
31331
31798
  setIsProcessing(true);
31332
31799
  await clientRef.current.send(nextMessage);
31333
31800
  }, [messageQueue]);
31334
- import_react25.useEffect(() => {
31801
+ import_react27.useEffect(() => {
31335
31802
  const initClient = async () => {
31336
31803
  try {
31337
31804
  const newClient = new EmbeddedClient(cwd2);
@@ -31341,6 +31808,19 @@ function App2({ cwd: cwd2 }) {
31341
31808
  responseRef.current += chunk.content;
31342
31809
  setCurrentResponse(responseRef.current);
31343
31810
  } else if (chunk.type === "tool_use" && chunk.toolCall) {
31811
+ if (responseRef.current.trim()) {
31812
+ setActivityLog((prev) => [
31813
+ ...prev,
31814
+ {
31815
+ id: generateId(),
31816
+ type: "text",
31817
+ content: responseRef.current,
31818
+ timestamp: now()
31819
+ }
31820
+ ]);
31821
+ setCurrentResponse("");
31822
+ responseRef.current = "";
31823
+ }
31344
31824
  toolCallsRef.current.push(chunk.toolCall);
31345
31825
  setActivityLog((prev) => [
31346
31826
  ...prev,
@@ -31352,7 +31832,6 @@ function App2({ cwd: cwd2 }) {
31352
31832
  }
31353
31833
  ]);
31354
31834
  setCurrentToolCall(chunk.toolCall);
31355
- setLastToolResult(undefined);
31356
31835
  } else if (chunk.type === "tool_result" && chunk.toolResult) {
31357
31836
  toolResultsRef.current.push(chunk.toolResult);
31358
31837
  setActivityLog((prev) => [
@@ -31364,33 +31843,49 @@ function App2({ cwd: cwd2 }) {
31364
31843
  timestamp: now()
31365
31844
  }
31366
31845
  ]);
31367
- setLastToolResult(chunk.toolResult);
31368
31846
  setCurrentToolCall(undefined);
31369
31847
  } else if (chunk.type === "error" && chunk.error) {
31370
31848
  setError(chunk.error);
31371
31849
  setIsProcessing(false);
31372
31850
  } else if (chunk.type === "usage" && chunk.usage) {
31373
31851
  setTokenUsage(chunk.usage);
31852
+ setCurrentTurnTokens((prev) => prev + (chunk.usage?.outputTokens || 0));
31374
31853
  } else if (chunk.type === "done") {
31375
- if (responseRef.current || toolCallsRef.current.length > 0) {
31854
+ if (responseRef.current.trim()) {
31855
+ setActivityLog((prev) => [
31856
+ ...prev,
31857
+ {
31858
+ id: generateId(),
31859
+ type: "text",
31860
+ content: responseRef.current,
31861
+ timestamp: now()
31862
+ }
31863
+ ]);
31864
+ }
31865
+ const fullContent = activityLog.filter((e) => e.type === "text").map((e) => e.content).join(`
31866
+ `) + (responseRef.current ? `
31867
+ ` + responseRef.current : "");
31868
+ if (fullContent.trim() || toolCallsRef.current.length > 0) {
31376
31869
  setMessages((prev) => [
31377
31870
  ...prev,
31378
31871
  {
31379
31872
  id: generateId(),
31380
31873
  role: "assistant",
31381
- content: responseRef.current,
31874
+ content: fullContent.trim(),
31382
31875
  timestamp: now(),
31383
31876
  toolCalls: toolCallsRef.current.length > 0 ? [...toolCallsRef.current] : undefined,
31384
31877
  toolResults: toolResultsRef.current.length > 0 ? [...toolResultsRef.current] : undefined
31385
31878
  }
31386
31879
  ]);
31387
- setCurrentResponse("");
31388
- responseRef.current = "";
31389
- toolCallsRef.current = [];
31390
- toolResultsRef.current = [];
31391
31880
  }
31881
+ setCurrentResponse("");
31882
+ responseRef.current = "";
31883
+ toolCallsRef.current = [];
31884
+ toolResultsRef.current = [];
31392
31885
  setCurrentToolCall(undefined);
31393
- setLastToolResult(undefined);
31886
+ setActivityLog([]);
31887
+ setProcessingStartTime(undefined);
31888
+ setCurrentTurnTokens(0);
31394
31889
  setIsProcessing(false);
31395
31890
  if (newClient) {
31396
31891
  setTokenUsage(newClient.getTokenUsage());
@@ -31411,11 +31906,17 @@ function App2({ cwd: cwd2 }) {
31411
31906
  };
31412
31907
  initClient();
31413
31908
  }, [cwd2]);
31414
- import_react25.useEffect(() => {
31909
+ import_react27.useEffect(() => {
31415
31910
  if (!isProcessing && messageQueue.length > 0) {
31416
31911
  processQueue();
31417
31912
  }
31418
31913
  }, [isProcessing, messageQueue.length, processQueue]);
31914
+ import_react27.useEffect(() => {
31915
+ if (autoScroll) {
31916
+ setScrollOffset(0);
31917
+ }
31918
+ }, [messages.length, autoScroll]);
31919
+ const maxVisibleMessages = 10;
31419
31920
  use_input_default((input, key) => {
31420
31921
  if (key.ctrl && input === "c") {
31421
31922
  if (isProcessing && client) {
@@ -31459,8 +31960,34 @@ function App2({ cwd: cwd2 }) {
31459
31960
  }
31460
31961
  setIsProcessing(false);
31461
31962
  }
31963
+ if (key.pageUp || key.shift && key.upArrow) {
31964
+ setScrollOffset((prev) => {
31965
+ const maxOffset = Math.max(0, messages.length - maxVisibleMessages);
31966
+ const newOffset = Math.min(prev + 3, maxOffset);
31967
+ if (newOffset > 0)
31968
+ setAutoScroll(false);
31969
+ return newOffset;
31970
+ });
31971
+ }
31972
+ if (key.pageDown || key.shift && key.downArrow) {
31973
+ setScrollOffset((prev) => {
31974
+ const newOffset = Math.max(0, prev - 3);
31975
+ if (newOffset === 0)
31976
+ setAutoScroll(true);
31977
+ return newOffset;
31978
+ });
31979
+ }
31980
+ if (key.ctrl && input === "u") {
31981
+ const maxOffset = Math.max(0, messages.length - maxVisibleMessages);
31982
+ setScrollOffset(maxOffset);
31983
+ setAutoScroll(false);
31984
+ }
31985
+ if (key.ctrl && input === "d") {
31986
+ setScrollOffset(0);
31987
+ setAutoScroll(true);
31988
+ }
31462
31989
  });
31463
- const handleSubmit = import_react25.useCallback(async (input, mode = "normal") => {
31990
+ const handleSubmit = import_react27.useCallback(async (input, mode = "normal") => {
31464
31991
  if (!client || !input.trim())
31465
31992
  return;
31466
31993
  const trimmedInput = input.trim();
@@ -31503,32 +32030,56 @@ function App2({ cwd: cwd2 }) {
31503
32030
  setCurrentToolCall(undefined);
31504
32031
  setLastToolResult(undefined);
31505
32032
  setActivityLog([]);
32033
+ setProcessingStartTime(Date.now());
32034
+ setCurrentTurnTokens(0);
31506
32035
  setIsProcessing(true);
31507
32036
  await client.send(trimmedInput);
31508
32037
  }, [client, isProcessing]);
31509
32038
  if (isInitializing) {
31510
- return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
32039
+ return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
31511
32040
  flexDirection: "column",
31512
32041
  padding: 1,
31513
- children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Spinner2, {
32042
+ children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Spinner2, {
31514
32043
  label: "Initializing..."
31515
32044
  }, undefined, false, undefined, this)
31516
32045
  }, undefined, false, undefined, this);
31517
32046
  }
31518
- return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
32047
+ const toolCallEntries = activityLog.filter((e) => e.type === "tool_call" && e.toolCall).map((e) => {
32048
+ const result = activityLog.find((r) => r.type === "tool_result" && r.toolResult?.toolCallId === e.toolCall?.id)?.toolResult;
32049
+ return { toolCall: e.toolCall, result };
32050
+ });
32051
+ const isThinking = isProcessing && !currentResponse && !currentToolCall && toolCallEntries.length === 0;
32052
+ return /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
31519
32053
  flexDirection: "column",
31520
32054
  padding: 1,
31521
32055
  children: [
31522
- /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Messages4, {
32056
+ scrollOffset > 0 && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
32057
+ children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
32058
+ dimColor: true,
32059
+ children: [
32060
+ "\u2191 ",
32061
+ scrollOffset,
32062
+ " more messages above (Shift+\u2193 or Ctrl+D to scroll down)"
32063
+ ]
32064
+ }, undefined, true, undefined, this)
32065
+ }, undefined, false, undefined, this),
32066
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Messages4, {
31523
32067
  messages,
31524
32068
  currentResponse: isProcessing ? currentResponse : undefined,
31525
- currentToolCall,
31526
- lastToolResult,
31527
- activityLog: isProcessing ? activityLog : []
32069
+ currentToolCall: undefined,
32070
+ lastToolResult: undefined,
32071
+ activityLog: isProcessing ? activityLog.filter((e) => e.type === "text") : [],
32072
+ scrollOffset,
32073
+ maxVisible: maxVisibleMessages
31528
32074
  }, undefined, false, undefined, this),
31529
- messageQueue.length > 0 && /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
32075
+ isProcessing && toolCallEntries.length > 0 && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(ToolCallBox, {
32076
+ entries: toolCallEntries,
32077
+ maxVisible: 3,
32078
+ isExpanded: toolsExpanded
32079
+ }, undefined, false, undefined, this),
32080
+ messageQueue.length > 0 && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
31530
32081
  marginY: 1,
31531
- children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
32082
+ children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
31532
32083
  dimColor: true,
31533
32084
  children: [
31534
32085
  messageQueue.length,
@@ -31538,9 +32089,9 @@ function App2({ cwd: cwd2 }) {
31538
32089
  ]
31539
32090
  }, undefined, true, undefined, this)
31540
32091
  }, undefined, false, undefined, this),
31541
- error && /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
32092
+ error && /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Box_default, {
31542
32093
  marginY: 1,
31543
- children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
32094
+ children: /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Text, {
31544
32095
  color: "red",
31545
32096
  children: [
31546
32097
  "Error: ",
@@ -31548,18 +32099,18 @@ function App2({ cwd: cwd2 }) {
31548
32099
  ]
31549
32100
  }, undefined, true, undefined, this)
31550
32101
  }, undefined, false, undefined, this),
31551
- isProcessing && !currentToolCall && !currentResponse && /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
31552
- marginY: 1,
31553
- children: /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Spinner2, {
31554
- label: "Thinking..."
31555
- }, undefined, false, undefined, this)
32102
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(ProcessingIndicator, {
32103
+ isProcessing,
32104
+ startTime: processingStartTime,
32105
+ tokenCount: currentTurnTokens,
32106
+ isThinking
31556
32107
  }, undefined, false, undefined, this),
31557
- /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Input, {
32108
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Input, {
31558
32109
  onSubmit: handleSubmit,
31559
32110
  isProcessing,
31560
32111
  queueLength: messageQueue.length
31561
32112
  }, undefined, false, undefined, this),
31562
- /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Status, {
32113
+ /* @__PURE__ */ jsx_dev_runtime8.jsxDEV(Status, {
31563
32114
  isProcessing,
31564
32115
  cwd: cwd2,
31565
32116
  queueLength: messageQueue.length,
@@ -31570,7 +32121,7 @@ function App2({ cwd: cwd2 }) {
31570
32121
  }
31571
32122
 
31572
32123
  // packages/terminal/src/index.tsx
31573
- var jsx_dev_runtime7 = __toESM(require_jsx_dev_runtime(), 1);
32124
+ var jsx_dev_runtime9 = __toESM(require_jsx_dev_runtime(), 1);
31574
32125
  var args = process.argv.slice(2);
31575
32126
  var options = {
31576
32127
  cwd: process.cwd(),
@@ -31578,7 +32129,7 @@ var options = {
31578
32129
  help: args.includes("--help") || args.includes("-h")
31579
32130
  };
31580
32131
  if (options.version) {
31581
- console.log("oldpal v0.1.9");
32132
+ console.log("oldpal v0.2.0");
31582
32133
  process.exit(0);
31583
32134
  }
31584
32135
  if (options.help) {
@@ -31602,11 +32153,11 @@ In interactive mode:
31602
32153
  `);
31603
32154
  process.exit(0);
31604
32155
  }
31605
- var { waitUntilExit } = render_default(/* @__PURE__ */ jsx_dev_runtime7.jsxDEV(App2, {
32156
+ var { waitUntilExit } = render_default(/* @__PURE__ */ jsx_dev_runtime9.jsxDEV(App2, {
31606
32157
  cwd: options.cwd
31607
32158
  }, undefined, false, undefined, this));
31608
32159
  waitUntilExit().then(() => {
31609
32160
  process.exit(0);
31610
32161
  });
31611
32162
 
31612
- //# debugId=307434E7D5151E5864756E2164756E21
32163
+ //# debugId=B53720C03AFA977C64756E2164756E21