@hasna/oldpal 0.1.7 → 0.1.9

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 existsSync2 } from "fs";
20454
- import { homedir as homedir3 } from "os";
20455
- import { join as join4 } from "path";
20453
+ import { readFileSync as readFileSync2, existsSync as existsSync4 } from "fs";
20454
+ import { homedir as homedir5 } from "os";
20455
+ import { join as join6 } from "path";
20456
20456
  function loadApiKeyFromSecrets() {
20457
- const secretsPath = join4(homedir3(), ".secrets");
20458
- if (existsSync2(secretsPath)) {
20457
+ const secretsPath = join6(homedir5(), ".secrets");
20458
+ if (existsSync4(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]+)["']?/);
@@ -29187,6 +29187,194 @@ class FilesystemTools {
29187
29187
  };
29188
29188
  }
29189
29189
 
29190
+ // packages/core/src/tools/web.ts
29191
+ class WebFetchTool {
29192
+ static tool = {
29193
+ name: "web_fetch",
29194
+ description: "Fetch content from a URL and return the text content. Useful for reading web pages, documentation, API responses, etc.",
29195
+ parameters: {
29196
+ type: "object",
29197
+ properties: {
29198
+ url: {
29199
+ type: "string",
29200
+ description: "The URL to fetch content from"
29201
+ },
29202
+ extract_type: {
29203
+ type: "string",
29204
+ description: 'What to extract: "text" for readable text, "html" for raw HTML, "json" for JSON response',
29205
+ enum: ["text", "html", "json"],
29206
+ default: "text"
29207
+ },
29208
+ timeout: {
29209
+ type: "number",
29210
+ description: "Timeout in milliseconds (default: 30000)"
29211
+ }
29212
+ },
29213
+ required: ["url"]
29214
+ }
29215
+ };
29216
+ static executor = async (input) => {
29217
+ const url = input.url;
29218
+ const extractType = input.extract_type || "text";
29219
+ const timeout = input.timeout || 30000;
29220
+ try {
29221
+ const parsedUrl = new URL(url);
29222
+ const hostname = parsedUrl.hostname;
29223
+ if (hostname === "localhost" || hostname === "127.0.0.1" || hostname.startsWith("192.168.") || hostname.startsWith("10.") || hostname.startsWith("172.")) {
29224
+ return "Error: Cannot fetch from local/private network addresses for security reasons";
29225
+ }
29226
+ const controller = new AbortController;
29227
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
29228
+ const response = await fetch(url, {
29229
+ signal: controller.signal,
29230
+ headers: {
29231
+ "User-Agent": "oldpal/1.0 (AI Assistant)",
29232
+ Accept: extractType === "json" ? "application/json" : "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
29233
+ }
29234
+ });
29235
+ clearTimeout(timeoutId);
29236
+ if (!response.ok) {
29237
+ return `Error: HTTP ${response.status} ${response.statusText}`;
29238
+ }
29239
+ const contentType = response.headers.get("content-type") || "";
29240
+ if (extractType === "json") {
29241
+ try {
29242
+ const json = await response.json();
29243
+ return JSON.stringify(json, null, 2);
29244
+ } catch {
29245
+ return "Error: Response is not valid JSON";
29246
+ }
29247
+ }
29248
+ const html = await response.text();
29249
+ if (extractType === "html") {
29250
+ const maxLength2 = 50000;
29251
+ if (html.length > maxLength2) {
29252
+ return html.slice(0, maxLength2) + `
29253
+
29254
+ [Content truncated...]`;
29255
+ }
29256
+ return html;
29257
+ }
29258
+ const text = extractReadableText(html);
29259
+ const maxLength = 30000;
29260
+ if (text.length > maxLength) {
29261
+ return text.slice(0, maxLength) + `
29262
+
29263
+ [Content truncated...]`;
29264
+ }
29265
+ return text || "No readable content found on page";
29266
+ } catch (error) {
29267
+ if (error instanceof Error) {
29268
+ if (error.name === "AbortError") {
29269
+ return `Error: Request timed out after ${timeout}ms`;
29270
+ }
29271
+ return `Error: ${error.message}`;
29272
+ }
29273
+ return `Error: ${String(error)}`;
29274
+ }
29275
+ };
29276
+ }
29277
+
29278
+ class WebSearchTool {
29279
+ static tool = {
29280
+ name: "web_search",
29281
+ description: "Search the web using DuckDuckGo and return results. Useful for finding current information, documentation, news, etc.",
29282
+ parameters: {
29283
+ type: "object",
29284
+ properties: {
29285
+ query: {
29286
+ type: "string",
29287
+ description: "The search query"
29288
+ },
29289
+ max_results: {
29290
+ type: "number",
29291
+ description: "Maximum number of results to return (default: 5, max: 10)"
29292
+ }
29293
+ },
29294
+ required: ["query"]
29295
+ }
29296
+ };
29297
+ static executor = async (input) => {
29298
+ const query = input.query;
29299
+ const maxResults = Math.min(input.max_results || 5, 10);
29300
+ try {
29301
+ const searchUrl = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
29302
+ const response = await fetch(searchUrl, {
29303
+ headers: {
29304
+ "User-Agent": "oldpal/1.0 (AI Assistant)",
29305
+ Accept: "text/html"
29306
+ }
29307
+ });
29308
+ if (!response.ok) {
29309
+ return `Error: Search request failed with HTTP ${response.status}`;
29310
+ }
29311
+ const html = await response.text();
29312
+ const results = parseDuckDuckGoResults(html, maxResults);
29313
+ if (results.length === 0) {
29314
+ return `No results found for "${query}"`;
29315
+ }
29316
+ let output = `Search results for "${query}":
29317
+
29318
+ `;
29319
+ for (let i = 0;i < results.length; i++) {
29320
+ const r = results[i];
29321
+ output += `${i + 1}. ${r.title}
29322
+ `;
29323
+ output += ` ${r.url}
29324
+ `;
29325
+ if (r.snippet) {
29326
+ output += ` ${r.snippet}
29327
+ `;
29328
+ }
29329
+ output += `
29330
+ `;
29331
+ }
29332
+ return output.trim();
29333
+ } catch (error) {
29334
+ return `Error: ${error instanceof Error ? error.message : String(error)}`;
29335
+ }
29336
+ };
29337
+ }
29338
+ function extractReadableText(html) {
29339
+ let text = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<noscript[^>]*>[\s\S]*?<\/noscript>/gi, "").replace(/<nav[^>]*>[\s\S]*?<\/nav>/gi, "").replace(/<footer[^>]*>[\s\S]*?<\/footer>/gi, "").replace(/<header[^>]*>[\s\S]*?<\/header>/gi, "");
29340
+ text = text.replace(/<\/?(p|div|br|h[1-6]|li|tr|blockquote)[^>]*>/gi, `
29341
+ `).replace(/<\/?[^>]+>/g, " ").replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, '"').replace(/&#39;/gi, "'").replace(/\s+/g, " ").replace(/\n\s*\n/g, `
29342
+
29343
+ `).trim();
29344
+ return text;
29345
+ }
29346
+ function parseDuckDuckGoResults(html, maxResults) {
29347
+ const results = [];
29348
+ const resultRegex = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>([^<]*)<\/a>[\s\S]*?<a[^>]*class="result__snippet"[^>]*>([^<]*)/gi;
29349
+ let match;
29350
+ while ((match = resultRegex.exec(html)) !== null && results.length < maxResults) {
29351
+ const url = decodeURIComponent(match[1].replace(/\/l\/\?uddg=/, "").split("&")[0]);
29352
+ const title = match[2].trim();
29353
+ const snippet = match[3].trim().replace(/&[^;]+;/g, " ");
29354
+ if (url && title && !url.startsWith("//duckduckgo.com")) {
29355
+ results.push({ title, url, snippet });
29356
+ }
29357
+ }
29358
+ if (results.length === 0) {
29359
+ const simpleRegex = /<a[^>]*href="(https?:\/\/[^"]+)"[^>]*class="[^"]*result[^"]*"[^>]*>([^<]+)/gi;
29360
+ while ((match = simpleRegex.exec(html)) !== null && results.length < maxResults) {
29361
+ const url = match[1];
29362
+ const title = match[2].trim();
29363
+ if (url && title) {
29364
+ results.push({ title, url, snippet: "" });
29365
+ }
29366
+ }
29367
+ }
29368
+ return results;
29369
+ }
29370
+
29371
+ class WebTools {
29372
+ static registerAll(registry) {
29373
+ registry.register(WebFetchTool.tool, WebFetchTool.executor);
29374
+ registry.register(WebSearchTool.tool, WebSearchTool.executor);
29375
+ }
29376
+ }
29377
+
29190
29378
  // packages/core/src/skills/loader.ts
29191
29379
  import { join as join3 } from "path";
29192
29380
  import { homedir as homedir2 } from "os";
@@ -29455,7 +29643,609 @@ class HookExecutor {
29455
29643
  return null;
29456
29644
  }
29457
29645
  }
29646
+ // 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";
29650
+
29651
+ class CommandLoader {
29652
+ commands = new Map;
29653
+ cwd;
29654
+ constructor(cwd2) {
29655
+ this.cwd = cwd2 || process.cwd();
29656
+ }
29657
+ async loadAll() {
29658
+ this.commands.clear();
29659
+ const globalDir = join4(homedir3(), ".oldpal", "commands");
29660
+ await this.loadFromDirectory(globalDir, "global");
29661
+ const projectDir = join4(this.cwd, ".oldpal", "commands");
29662
+ await this.loadFromDirectory(projectDir, "project");
29663
+ }
29664
+ async loadFromDirectory(dir, source, prefix = "") {
29665
+ if (!existsSync2(dir))
29666
+ return;
29667
+ const entries = readdirSync(dir);
29668
+ for (const entry of entries) {
29669
+ const fullPath = join4(dir, entry);
29670
+ const stat = statSync(fullPath);
29671
+ if (stat.isDirectory()) {
29672
+ const newPrefix = prefix ? `${prefix}:${entry}` : entry;
29673
+ await this.loadFromDirectory(fullPath, source, newPrefix);
29674
+ } else if (stat.isFile() && extname(entry) === ".md") {
29675
+ const command = await this.loadCommandFile(fullPath, prefix);
29676
+ if (command) {
29677
+ this.commands.set(command.name, command);
29678
+ }
29679
+ }
29680
+ }
29681
+ }
29682
+ async loadCommandFile(filePath, prefix) {
29683
+ try {
29684
+ const content = await Bun.file(filePath).text();
29685
+ const { frontmatter, body } = this.parseFrontmatter(content);
29686
+ const fileName = basename(filePath, ".md");
29687
+ const name = frontmatter.name || (prefix ? `${prefix}:${fileName}` : fileName);
29688
+ return {
29689
+ name,
29690
+ description: frontmatter.description || `Run the ${name} command`,
29691
+ tags: frontmatter.tags,
29692
+ allowedTools: frontmatter["allowed-tools"]?.split(",").map((t) => t.trim()),
29693
+ content: body,
29694
+ filePath,
29695
+ builtin: false
29696
+ };
29697
+ } catch (error) {
29698
+ console.error(`Failed to load command from ${filePath}:`, error);
29699
+ return null;
29700
+ }
29701
+ }
29702
+ parseFrontmatter(content) {
29703
+ const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
29704
+ const match = content.match(frontmatterRegex);
29705
+ if (!match) {
29706
+ return { frontmatter: {}, body: content };
29707
+ }
29708
+ const [, yamlContent, body] = match;
29709
+ const frontmatter = {};
29710
+ const lines = yamlContent.split(`
29711
+ `);
29712
+ for (const line of lines) {
29713
+ const colonIndex = line.indexOf(":");
29714
+ if (colonIndex === -1)
29715
+ continue;
29716
+ const key = line.slice(0, colonIndex).trim();
29717
+ let value = line.slice(colonIndex + 1).trim();
29718
+ if (value.startsWith("[") && value.endsWith("]")) {
29719
+ const arrayContent = value.slice(1, -1);
29720
+ frontmatter[key] = arrayContent.split(",").map((v) => v.trim());
29721
+ } else {
29722
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
29723
+ value = value.slice(1, -1);
29724
+ }
29725
+ frontmatter[key] = value;
29726
+ }
29727
+ }
29728
+ return { frontmatter, body: body.trim() };
29729
+ }
29730
+ register(command) {
29731
+ this.commands.set(command.name, command);
29732
+ }
29733
+ getCommand(name) {
29734
+ return this.commands.get(name);
29735
+ }
29736
+ getCommands() {
29737
+ return Array.from(this.commands.values());
29738
+ }
29739
+ hasCommand(name) {
29740
+ return this.commands.has(name);
29741
+ }
29742
+ findMatching(partial) {
29743
+ const lower = partial.toLowerCase();
29744
+ return this.getCommands().filter((cmd) => cmd.name.toLowerCase().startsWith(lower) || cmd.description.toLowerCase().includes(lower));
29745
+ }
29746
+ }
29747
+ // packages/core/src/commands/executor.ts
29748
+ class CommandExecutor {
29749
+ loader;
29750
+ constructor(loader) {
29751
+ this.loader = loader;
29752
+ }
29753
+ parseCommand(input) {
29754
+ const trimmed = input.trim();
29755
+ if (!trimmed.startsWith("/")) {
29756
+ return null;
29757
+ }
29758
+ const match = trimmed.match(/^\/(\S+)(?:\s+(.*))?$/);
29759
+ if (!match) {
29760
+ return null;
29761
+ }
29762
+ return {
29763
+ name: match[1],
29764
+ args: match[2] || ""
29765
+ };
29766
+ }
29767
+ isCommand(input) {
29768
+ return this.parseCommand(input) !== null;
29769
+ }
29770
+ async execute(input, context) {
29771
+ const parsed = this.parseCommand(input);
29772
+ if (!parsed) {
29773
+ return { handled: false };
29774
+ }
29775
+ const command = this.loader.getCommand(parsed.name);
29776
+ if (!command) {
29777
+ context.emit("text", `Unknown command: /${parsed.name}
29778
+
29779
+ Use /help to see available commands.
29780
+ `);
29781
+ context.emit("done");
29782
+ return { handled: true };
29783
+ }
29784
+ if (command.selfHandled && command.handler) {
29785
+ return command.handler(parsed.args, context);
29786
+ }
29787
+ const prompt = await this.preparePrompt(command, parsed.args);
29788
+ return {
29789
+ handled: false,
29790
+ prompt
29791
+ };
29792
+ }
29793
+ async preparePrompt(command, args) {
29794
+ let content = command.content;
29795
+ content = content.replace(/\$ARGUMENTS/g, args || "(no arguments provided)");
29796
+ content = await this.processShellCommands(content);
29797
+ return content;
29798
+ }
29799
+ async processShellCommands(content) {
29800
+ const lines = content.split(`
29801
+ `);
29802
+ const processedLines = [];
29803
+ for (const line of lines) {
29804
+ const trimmed = line.trim();
29805
+ if (trimmed.startsWith("!")) {
29806
+ const command = trimmed.slice(1).trim();
29807
+ const output = await this.executeShell(command);
29808
+ processedLines.push(`\`\`\`
29809
+ ${output}
29810
+ \`\`\``);
29811
+ } else {
29812
+ processedLines.push(line);
29813
+ }
29814
+ }
29815
+ return processedLines.join(`
29816
+ `);
29817
+ }
29818
+ async executeShell(command) {
29819
+ try {
29820
+ const proc = Bun.spawn(["bash", "-c", command], {
29821
+ stdout: "pipe",
29822
+ stderr: "pipe"
29823
+ });
29824
+ const [stdout, stderr] = await Promise.all([
29825
+ new Response(proc.stdout).text(),
29826
+ new Response(proc.stderr).text()
29827
+ ]);
29828
+ const exitCode = await proc.exited;
29829
+ if (exitCode !== 0 && stderr) {
29830
+ return `Error (exit ${exitCode}):
29831
+ ${stderr}`;
29832
+ }
29833
+ return stdout.trim() || "(no output)";
29834
+ } catch (error) {
29835
+ return `Error: ${error instanceof Error ? error.message : String(error)}`;
29836
+ }
29837
+ }
29838
+ getSuggestions(partial) {
29839
+ if (!partial.startsWith("/")) {
29840
+ return [];
29841
+ }
29842
+ const name = partial.slice(1).toLowerCase();
29843
+ return this.loader.findMatching(name);
29844
+ }
29845
+ }
29846
+ // 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";
29850
+
29851
+ class BuiltinCommands {
29852
+ tokenUsage = {
29853
+ inputTokens: 0,
29854
+ outputTokens: 0,
29855
+ totalTokens: 0,
29856
+ maxContextTokens: 200000
29857
+ };
29858
+ registerAll(loader) {
29859
+ loader.register(this.helpCommand(loader));
29860
+ loader.register(this.clearCommand());
29861
+ loader.register(this.statusCommand());
29862
+ loader.register(this.compactCommand());
29863
+ loader.register(this.configCommand());
29864
+ loader.register(this.initCommand());
29865
+ loader.register(this.costCommand());
29866
+ loader.register(this.modelCommand());
29867
+ loader.register(this.memoryCommand());
29868
+ loader.register(this.bugCommand());
29869
+ loader.register(this.prCommand());
29870
+ loader.register(this.reviewCommand());
29871
+ }
29872
+ updateTokenUsage(usage) {
29873
+ Object.assign(this.tokenUsage, usage);
29874
+ }
29875
+ getTokenUsage() {
29876
+ return { ...this.tokenUsage };
29877
+ }
29878
+ helpCommand(loader) {
29879
+ return {
29880
+ name: "help",
29881
+ description: "Show available slash commands",
29882
+ builtin: true,
29883
+ selfHandled: true,
29884
+ content: "",
29885
+ handler: async (args, context) => {
29886
+ const commands = loader.getCommands();
29887
+ const builtinCmds = commands.filter((c) => c.builtin);
29888
+ const customCmds = commands.filter((c) => !c.builtin);
29889
+ let message = `
29890
+ **Available Slash Commands**
29891
+
29892
+ `;
29893
+ if (builtinCmds.length > 0) {
29894
+ message += `**Built-in Commands:**
29895
+ `;
29896
+ for (const cmd of builtinCmds.sort((a, b) => a.name.localeCompare(b.name))) {
29897
+ message += ` /${cmd.name} - ${cmd.description}
29898
+ `;
29899
+ }
29900
+ message += `
29901
+ `;
29902
+ }
29903
+ if (customCmds.length > 0) {
29904
+ message += `**Custom Commands:**
29905
+ `;
29906
+ for (const cmd of customCmds.sort((a, b) => a.name.localeCompare(b.name))) {
29907
+ message += ` /${cmd.name} - ${cmd.description}
29908
+ `;
29909
+ }
29910
+ message += `
29911
+ `;
29912
+ }
29913
+ message += `**Tips:**
29914
+ `;
29915
+ message += ` - Create custom commands in .oldpal/commands/*.md
29916
+ `;
29917
+ message += ` - Global commands go in ~/.oldpal/commands/*.md
29918
+ `;
29919
+ message += ` - Use /init to create a starter command
29920
+ `;
29921
+ context.emit("text", message);
29922
+ context.emit("done");
29923
+ return { handled: true };
29924
+ }
29925
+ };
29926
+ }
29927
+ clearCommand() {
29928
+ return {
29929
+ name: "clear",
29930
+ description: "Clear conversation history and start fresh",
29931
+ builtin: true,
29932
+ selfHandled: true,
29933
+ content: "",
29934
+ handler: async (args, context) => {
29935
+ context.clearMessages();
29936
+ this.tokenUsage.inputTokens = 0;
29937
+ this.tokenUsage.outputTokens = 0;
29938
+ this.tokenUsage.totalTokens = 0;
29939
+ context.emit("text", `Conversation cleared. Starting fresh.
29940
+ `);
29941
+ context.emit("done");
29942
+ return { handled: true, clearConversation: true };
29943
+ }
29944
+ };
29945
+ }
29946
+ statusCommand() {
29947
+ return {
29948
+ name: "status",
29949
+ description: "Show current session status and token usage",
29950
+ builtin: true,
29951
+ selfHandled: true,
29952
+ content: "",
29953
+ handler: async (args, context) => {
29954
+ const usage = this.tokenUsage;
29955
+ const usedPercent = Math.round(usage.totalTokens / usage.maxContextTokens * 100);
29956
+ let message = `
29957
+ **Session Status**
29958
+
29959
+ `;
29960
+ message += `**Working Directory:** ${context.cwd}
29961
+ `;
29962
+ message += `**Session ID:** ${context.sessionId}
29963
+ `;
29964
+ message += `**Messages:** ${context.messages.length}
29965
+ `;
29966
+ message += `**Available Tools:** ${context.tools.length}
29967
+
29968
+ `;
29969
+ message += `**Token Usage:**
29970
+ `;
29971
+ message += ` Input: ${usage.inputTokens.toLocaleString()}
29972
+ `;
29973
+ message += ` Output: ${usage.outputTokens.toLocaleString()}
29974
+ `;
29975
+ message += ` Total: ${usage.totalTokens.toLocaleString()} / ${usage.maxContextTokens.toLocaleString()} (${usedPercent}%)
29976
+ `;
29977
+ if (usage.cacheReadTokens || usage.cacheWriteTokens) {
29978
+ message += ` Cache Read: ${(usage.cacheReadTokens || 0).toLocaleString()}
29979
+ `;
29980
+ message += ` Cache Write: ${(usage.cacheWriteTokens || 0).toLocaleString()}
29981
+ `;
29982
+ }
29983
+ const barLength = 30;
29984
+ const filledLength = Math.round(usedPercent / 100 * barLength);
29985
+ const bar = "\u2588".repeat(filledLength) + "\u2591".repeat(barLength - filledLength);
29986
+ message += `
29987
+ [${bar}] ${usedPercent}%
29988
+ `;
29989
+ context.emit("text", message);
29990
+ context.emit("done");
29991
+ return { handled: true };
29992
+ }
29993
+ };
29994
+ }
29995
+ compactCommand() {
29996
+ return {
29997
+ name: "compact",
29998
+ description: "Summarize conversation to save context space",
29999
+ builtin: true,
30000
+ selfHandled: false,
30001
+ content: `Please summarize our conversation so far into a concise format that preserves:
30002
+ 1. Key decisions made
30003
+ 2. Important context about the codebase
30004
+ 3. Current task/goal we're working on
30005
+ 4. Any constraints or requirements mentioned
30006
+
30007
+ Format the summary as a brief bullet-point list. This summary will replace the conversation history to save context space.`
30008
+ };
30009
+ }
30010
+ configCommand() {
30011
+ return {
30012
+ name: "config",
30013
+ description: "Show current configuration",
30014
+ builtin: true,
30015
+ selfHandled: true,
30016
+ content: "",
30017
+ handler: async (args, context) => {
30018
+ const configPaths = [
30019
+ join5(context.cwd, ".oldpal", "config.json"),
30020
+ join5(homedir4(), ".oldpal", "config.json")
30021
+ ];
30022
+ let message = `
30023
+ **Configuration**
30024
+
30025
+ `;
30026
+ message += `**Config File Locations:**
30027
+ `;
30028
+ for (const path of configPaths) {
30029
+ const exists = existsSync3(path);
30030
+ message += ` ${exists ? "\u2713" : "\u25CB"} ${path}
30031
+ `;
30032
+ }
30033
+ message += `
30034
+ **Commands Directories:**
30035
+ `;
30036
+ message += ` - Project: ${join5(context.cwd, ".oldpal", "commands")}
30037
+ `;
30038
+ message += ` - Global: ${join5(homedir4(), ".oldpal", "commands")}
30039
+ `;
30040
+ context.emit("text", message);
30041
+ context.emit("done");
30042
+ return { handled: true };
30043
+ }
30044
+ };
30045
+ }
30046
+ initCommand() {
30047
+ return {
30048
+ name: "init",
30049
+ description: "Initialize oldpal config and create example command",
30050
+ builtin: true,
30051
+ selfHandled: true,
30052
+ content: "",
30053
+ handler: async (args, context) => {
30054
+ const commandsDir = join5(context.cwd, ".oldpal", "commands");
30055
+ mkdirSync(commandsDir, { recursive: true });
30056
+ const exampleCommand = `---
30057
+ name: review
30058
+ description: Review code changes for issues and improvements
30059
+ tags: [code, review]
30060
+ ---
30061
+
30062
+ # Code Review
30063
+
30064
+ Please review the current code changes and provide feedback on:
30065
+
30066
+ 1. **Code Quality**
30067
+ - Readability and maintainability
30068
+ - Following project conventions
30069
+ - Proper error handling
30070
+
30071
+ 2. **Potential Issues**
30072
+ - Security vulnerabilities
30073
+ - Performance concerns
30074
+ - Edge cases not handled
30075
+
30076
+ 3. **Suggestions**
30077
+ - Improvements to consider
30078
+ - Best practices to apply
30079
+ - Documentation needs
30080
+
30081
+ If there are staged git changes, focus on those. Otherwise, ask what code to review.
30082
+ `;
30083
+ const examplePath = join5(commandsDir, "review.md");
30084
+ if (!existsSync3(examplePath)) {
30085
+ writeFileSync(examplePath, exampleCommand);
30086
+ }
30087
+ let message = `
30088
+ **Initialized oldpal**
30089
+
30090
+ `;
30091
+ message += `Created: ${commandsDir}
30092
+ `;
30093
+ message += `Example: ${examplePath}
30094
+
30095
+ `;
30096
+ message += `You can now:
30097
+ `;
30098
+ message += ` - Add custom commands to .oldpal/commands/
30099
+ `;
30100
+ message += ` - Use /review to try the example command
30101
+ `;
30102
+ message += ` - Run /help to see all available commands
30103
+ `;
30104
+ context.emit("text", message);
30105
+ context.emit("done");
30106
+ return { handled: true };
30107
+ }
30108
+ };
30109
+ }
30110
+ costCommand() {
30111
+ return {
30112
+ name: "cost",
30113
+ description: "Show estimated API cost for this session",
30114
+ builtin: true,
30115
+ selfHandled: true,
30116
+ content: "",
30117
+ handler: async (args, context) => {
30118
+ const usage = this.tokenUsage;
30119
+ const inputCostPer1M = 3;
30120
+ const outputCostPer1M = 15;
30121
+ const inputCost = usage.inputTokens / 1e6 * inputCostPer1M;
30122
+ const outputCost = usage.outputTokens / 1e6 * outputCostPer1M;
30123
+ const totalCost = inputCost + outputCost;
30124
+ const cacheReadCostPer1M = 0.3;
30125
+ const cacheSavings = usage.cacheReadTokens ? usage.cacheReadTokens / 1e6 * (inputCostPer1M - cacheReadCostPer1M) : 0;
30126
+ let message = `
30127
+ **Estimated Session Cost**
30128
+
30129
+ `;
30130
+ message += `Input tokens: ${usage.inputTokens.toLocaleString()} (~$${inputCost.toFixed(4)})
30131
+ `;
30132
+ message += `Output tokens: ${usage.outputTokens.toLocaleString()} (~$${outputCost.toFixed(4)})
30133
+ `;
30134
+ message += `**Total: ~$${totalCost.toFixed(4)}**
30135
+ `;
30136
+ if (cacheSavings > 0) {
30137
+ message += `
30138
+ Cache savings: ~$${cacheSavings.toFixed(4)}
30139
+ `;
30140
+ }
30141
+ message += `
30142
+ *Based on Claude 3.5 Sonnet pricing*
30143
+ `;
30144
+ context.emit("text", message);
30145
+ context.emit("done");
30146
+ return { handled: true };
30147
+ }
30148
+ };
30149
+ }
30150
+ modelCommand() {
30151
+ return {
30152
+ name: "model",
30153
+ description: "Show current model information",
30154
+ builtin: true,
30155
+ selfHandled: true,
30156
+ content: "",
30157
+ handler: async (args, context) => {
30158
+ let message = `
30159
+ **Model Information**
30160
+
30161
+ `;
30162
+ message += `Current model: claude-3-5-sonnet-20241022
30163
+ `;
30164
+ message += `Context window: 200,000 tokens
30165
+ `;
30166
+ message += `Max output: 8,192 tokens
30167
+
30168
+ `;
30169
+ message += `*Model selection coming in a future update*
30170
+ `;
30171
+ context.emit("text", message);
30172
+ context.emit("done");
30173
+ return { handled: true };
30174
+ }
30175
+ };
30176
+ }
30177
+ memoryCommand() {
30178
+ return {
30179
+ name: "memory",
30180
+ description: "Show conversation summary and key memories",
30181
+ builtin: true,
30182
+ selfHandled: false,
30183
+ content: `Please provide a summary of our conversation so far, including:
30184
+
30185
+ 1. **Key Context** - What you know about this project/codebase
30186
+ 2. **Current Task** - What we're working on
30187
+ 3. **Decisions Made** - Any choices or agreements from our discussion
30188
+ 4. **Open Items** - Things we mentioned but haven't addressed yet
30189
+
30190
+ Keep it concise but comprehensive.`
30191
+ };
30192
+ }
30193
+ bugCommand() {
30194
+ return {
30195
+ name: "bug",
30196
+ description: "Analyze and help fix a bug",
30197
+ builtin: true,
30198
+ selfHandled: false,
30199
+ content: `Help me debug an issue. $ARGUMENTS
30200
+
30201
+ Please:
30202
+ 1. Understand the bug/error described
30203
+ 2. Identify likely causes
30204
+ 3. Search relevant code files
30205
+ 4. Propose a fix with code changes
30206
+
30207
+ If no bug is described, ask me to describe the issue I'm experiencing.`
30208
+ };
30209
+ }
30210
+ prCommand() {
30211
+ return {
30212
+ name: "pr",
30213
+ description: "Create a pull request for current changes",
30214
+ builtin: true,
30215
+ selfHandled: false,
30216
+ content: `Help me create a pull request for the current changes.
30217
+
30218
+ 1. First, check git status and staged changes
30219
+ 2. Review the diff to understand what changed
30220
+ 3. Write a clear PR title (max 72 chars)
30221
+ 4. Write a description with:
30222
+ - Summary of changes
30223
+ - Motivation/context
30224
+ - Testing done
30225
+ - Any notes for reviewers
30226
+
30227
+ Then create the PR using the gh CLI.`
30228
+ };
30229
+ }
30230
+ reviewCommand() {
30231
+ return {
30232
+ name: "review",
30233
+ description: "Review code changes for issues",
30234
+ builtin: true,
30235
+ selfHandled: false,
30236
+ content: `Review the current code changes. $ARGUMENTS
30237
+
30238
+ Check for:
30239
+ 1. **Bugs** - Logic errors, edge cases, null checks
30240
+ 2. **Security** - Input validation, injection risks, secrets
30241
+ 3. **Performance** - N+1 queries, unnecessary loops, memory leaks
30242
+ 4. **Style** - Naming, formatting, code organization
30243
+ 5. **Tests** - Coverage, edge cases, assertions
29458
30244
 
30245
+ If there are staged changes, review those. Otherwise, ask what to review.`
30246
+ };
30247
+ }
30248
+ }
29459
30249
  // packages/core/src/llm/client.ts
29460
30250
  async function createLLMClient(config) {
29461
30251
  if (config.provider === "anthropic") {
@@ -29466,8 +30256,8 @@ async function createLLMClient(config) {
29466
30256
  }
29467
30257
 
29468
30258
  // packages/core/src/config.ts
29469
- import { join as join5 } from "path";
29470
- import { homedir as homedir4 } from "os";
30259
+ import { join as join7 } from "path";
30260
+ import { homedir as homedir6 } from "os";
29471
30261
  var DEFAULT_CONFIG = {
29472
30262
  llm: {
29473
30263
  provider: "anthropic",
@@ -29497,13 +30287,13 @@ var DEFAULT_CONFIG = {
29497
30287
  ]
29498
30288
  };
29499
30289
  function getConfigDir() {
29500
- return join5(homedir4(), ".oldpal");
30290
+ return join7(homedir6(), ".oldpal");
29501
30291
  }
29502
30292
  function getConfigPath(filename) {
29503
- return join5(getConfigDir(), filename);
30293
+ return join7(getConfigDir(), filename);
29504
30294
  }
29505
30295
  function getProjectConfigDir(cwd2 = process.cwd()) {
29506
- return join5(cwd2, ".oldpal");
30296
+ return join7(cwd2, ".oldpal");
29507
30297
  }
29508
30298
  async function loadConfig(cwd2 = process.cwd()) {
29509
30299
  const config = { ...DEFAULT_CONFIG };
@@ -29516,7 +30306,7 @@ async function loadConfig(cwd2 = process.cwd()) {
29516
30306
  if (userConfig.voice)
29517
30307
  config.voice = { ...config.voice, ...userConfig.voice };
29518
30308
  }
29519
- const projectConfigPath = join5(getProjectConfigDir(cwd2), "settings.json");
30309
+ const projectConfigPath = join7(getProjectConfigDir(cwd2), "settings.json");
29520
30310
  const projectConfig = await loadJsonFile(projectConfigPath);
29521
30311
  if (projectConfig) {
29522
30312
  Object.assign(config, projectConfig);
@@ -29525,7 +30315,7 @@ async function loadConfig(cwd2 = process.cwd()) {
29525
30315
  if (projectConfig.voice)
29526
30316
  config.voice = { ...config.voice, ...projectConfig.voice };
29527
30317
  }
29528
- const localConfigPath = join5(getProjectConfigDir(cwd2), "settings.local.json");
30318
+ const localConfigPath = join7(getProjectConfigDir(cwd2), "settings.local.json");
29529
30319
  const localConfig = await loadJsonFile(localConfigPath);
29530
30320
  if (localConfig) {
29531
30321
  Object.assign(config, localConfig);
@@ -29543,7 +30333,7 @@ async function loadHooksConfig(cwd2 = process.cwd()) {
29543
30333
  if (userHooks?.hooks) {
29544
30334
  mergeHooks(hooks, userHooks.hooks);
29545
30335
  }
29546
- const projectHooksPath = join5(getProjectConfigDir(cwd2), "hooks.json");
30336
+ const projectHooksPath = join7(getProjectConfigDir(cwd2), "hooks.json");
29547
30337
  const projectHooks = await loadJsonFile(projectHooksPath);
29548
30338
  if (projectHooks?.hooks) {
29549
30339
  mergeHooks(hooks, projectHooks.hooks);
@@ -29579,16 +30369,22 @@ class AgentLoop {
29579
30369
  skillExecutor;
29580
30370
  hookLoader;
29581
30371
  hookExecutor;
30372
+ commandLoader;
30373
+ commandExecutor;
30374
+ builtinCommands;
29582
30375
  llmClient = null;
29583
30376
  config = null;
29584
30377
  cwd;
30378
+ sessionId;
29585
30379
  isRunning = false;
29586
30380
  shouldStop = false;
29587
30381
  onChunk;
29588
30382
  onToolStart;
29589
30383
  onToolEnd;
30384
+ onTokenUsage;
29590
30385
  constructor(options = {}) {
29591
30386
  this.cwd = options.cwd || process.cwd();
30387
+ this.sessionId = options.sessionId || generateId();
29592
30388
  this.context = new AgentContext;
29593
30389
  this.toolRegistry = new ToolRegistry;
29594
30390
  this.connectorBridge = new ConnectorBridge;
@@ -29596,22 +30392,29 @@ class AgentLoop {
29596
30392
  this.skillExecutor = new SkillExecutor;
29597
30393
  this.hookLoader = new HookLoader;
29598
30394
  this.hookExecutor = new HookExecutor;
30395
+ this.commandLoader = new CommandLoader(this.cwd);
30396
+ this.commandExecutor = new CommandExecutor(this.commandLoader);
30397
+ this.builtinCommands = new BuiltinCommands;
29599
30398
  this.onChunk = options.onChunk;
29600
30399
  this.onToolStart = options.onToolStart;
29601
30400
  this.onToolEnd = options.onToolEnd;
30401
+ this.onTokenUsage = options.onTokenUsage;
29602
30402
  }
29603
30403
  async initialize() {
29604
30404
  this.config = await loadConfig(this.cwd);
29605
30405
  this.llmClient = await createLLMClient(this.config.llm);
29606
30406
  this.toolRegistry.register(BashTool.tool, BashTool.executor);
29607
30407
  FilesystemTools.registerAll(this.toolRegistry);
30408
+ WebTools.registerAll(this.toolRegistry);
29608
30409
  await this.connectorBridge.discover(this.config.connectors);
29609
30410
  this.connectorBridge.registerAll(this.toolRegistry);
29610
30411
  await this.skillLoader.loadAll(this.cwd);
30412
+ await this.commandLoader.loadAll();
30413
+ this.builtinCommands.registerAll(this.commandLoader);
29611
30414
  const hooksConfig = await loadHooksConfig(this.cwd);
29612
30415
  this.hookLoader.load(hooksConfig);
29613
30416
  await this.hookExecutor.execute(this.hookLoader.getHooks("SessionStart"), {
29614
- session_id: generateId(),
30417
+ session_id: this.sessionId,
29615
30418
  hook_event_name: "SessionStart",
29616
30419
  cwd: this.cwd
29617
30420
  });
@@ -29627,7 +30430,7 @@ class AgentLoop {
29627
30430
  this.shouldStop = false;
29628
30431
  try {
29629
30432
  const promptHookResult = await this.hookExecutor.execute(this.hookLoader.getHooks("UserPromptSubmit"), {
29630
- session_id: generateId(),
30433
+ session_id: this.sessionId,
29631
30434
  hook_event_name: "UserPromptSubmit",
29632
30435
  cwd: this.cwd,
29633
30436
  prompt: userMessage
@@ -29636,6 +30439,18 @@ class AgentLoop {
29636
30439
  this.emit({ type: "error", error: promptHookResult.stopReason || "Blocked by hook" });
29637
30440
  return;
29638
30441
  }
30442
+ if (userMessage.startsWith("/")) {
30443
+ const commandResult = await this.handleCommand(userMessage);
30444
+ if (commandResult.handled) {
30445
+ if (commandResult.clearConversation) {
30446
+ this.context = new AgentContext;
30447
+ }
30448
+ return;
30449
+ }
30450
+ if (commandResult.prompt) {
30451
+ userMessage = commandResult.prompt;
30452
+ }
30453
+ }
29639
30454
  if (userMessage.startsWith("/")) {
29640
30455
  const handled = await this.handleSkillInvocation(userMessage);
29641
30456
  if (handled)
@@ -29716,6 +30531,30 @@ class AgentLoop {
29716
30531
  }
29717
30532
  return results;
29718
30533
  }
30534
+ async handleCommand(message) {
30535
+ const context = {
30536
+ cwd: this.cwd,
30537
+ sessionId: this.sessionId,
30538
+ messages: this.context.getMessages(),
30539
+ tools: this.toolRegistry.getTools(),
30540
+ clearMessages: () => {
30541
+ this.context = new AgentContext;
30542
+ },
30543
+ addSystemMessage: (content) => {
30544
+ this.context.addSystemMessage(content);
30545
+ },
30546
+ emit: (type, content) => {
30547
+ if (type === "text" && content) {
30548
+ this.emit({ type: "text", content });
30549
+ } else if (type === "done") {
30550
+ this.emit({ type: "done" });
30551
+ } else if (type === "error" && content) {
30552
+ this.emit({ type: "error", error: content });
30553
+ }
30554
+ }
30555
+ };
30556
+ return this.commandExecutor.execute(message, context);
30557
+ }
29719
30558
  async handleSkillInvocation(message) {
29720
30559
  const match = message.match(/^\/(\S+)(?:\s+(.*))?$/);
29721
30560
  if (!match)
@@ -29747,18 +30586,34 @@ class AgentLoop {
29747
30586
  getSkills() {
29748
30587
  return this.skillLoader.getSkills();
29749
30588
  }
30589
+ getCommands() {
30590
+ return this.commandLoader.getCommands();
30591
+ }
30592
+ getTokenUsage() {
30593
+ return this.builtinCommands.getTokenUsage();
30594
+ }
30595
+ updateTokenUsage(usage) {
30596
+ this.builtinCommands.updateTokenUsage(usage);
30597
+ this.onTokenUsage?.(this.builtinCommands.getTokenUsage());
30598
+ }
29750
30599
  isProcessing() {
29751
30600
  return this.isRunning;
29752
30601
  }
30602
+ getSessionId() {
30603
+ return this.sessionId;
30604
+ }
30605
+ clearConversation() {
30606
+ this.context = new AgentContext;
30607
+ }
29753
30608
  }
29754
30609
 
29755
30610
  // packages/core/src/index.ts
29756
30611
  init_anthropic();
29757
30612
 
29758
30613
  // packages/core/src/logger.ts
29759
- import { existsSync as existsSync3, mkdirSync, appendFileSync } from "fs";
29760
- import { join as join6 } from "path";
29761
- import { homedir as homedir5 } from "os";
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";
29762
30617
 
29763
30618
  class Logger {
29764
30619
  logDir;
@@ -29766,14 +30621,14 @@ class Logger {
29766
30621
  sessionId;
29767
30622
  constructor(sessionId) {
29768
30623
  this.sessionId = sessionId;
29769
- this.logDir = join6(homedir5(), ".oldpal", "logs");
30624
+ this.logDir = join8(homedir7(), ".oldpal", "logs");
29770
30625
  this.ensureDir(this.logDir);
29771
30626
  const date = new Date().toISOString().split("T")[0];
29772
- this.logFile = join6(this.logDir, `${date}.log`);
30627
+ this.logFile = join8(this.logDir, `${date}.log`);
29773
30628
  }
29774
30629
  ensureDir(dir) {
29775
- if (!existsSync3(dir)) {
29776
- mkdirSync(dir, { recursive: true });
30630
+ if (!existsSync5(dir)) {
30631
+ mkdirSync2(dir, { recursive: true });
29777
30632
  }
29778
30633
  }
29779
30634
  write(level, message, data) {
@@ -29812,13 +30667,13 @@ class SessionStorage {
29812
30667
  sessionId;
29813
30668
  constructor(sessionId) {
29814
30669
  this.sessionId = sessionId;
29815
- this.sessionsDir = join6(homedir5(), ".oldpal", "sessions");
30670
+ this.sessionsDir = join8(homedir7(), ".oldpal", "sessions");
29816
30671
  this.ensureDir(this.sessionsDir);
29817
- this.sessionFile = join6(this.sessionsDir, `${sessionId}.json`);
30672
+ this.sessionFile = join8(this.sessionsDir, `${sessionId}.json`);
29818
30673
  }
29819
30674
  ensureDir(dir) {
29820
- if (!existsSync3(dir)) {
29821
- mkdirSync(dir, { recursive: true });
30675
+ if (!existsSync5(dir)) {
30676
+ mkdirSync2(dir, { recursive: true });
29822
30677
  }
29823
30678
  }
29824
30679
  save(data) {
@@ -29831,16 +30686,16 @@ class SessionStorage {
29831
30686
  }
29832
30687
  }
29833
30688
  function initOldpalDir() {
29834
- const baseDir = join6(homedir5(), ".oldpal");
30689
+ const baseDir = join8(homedir7(), ".oldpal");
29835
30690
  const dirs = [
29836
30691
  baseDir,
29837
- join6(baseDir, "sessions"),
29838
- join6(baseDir, "logs"),
29839
- join6(baseDir, "skills")
30692
+ join8(baseDir, "sessions"),
30693
+ join8(baseDir, "logs"),
30694
+ join8(baseDir, "skills")
29840
30695
  ];
29841
30696
  for (const dir of dirs) {
29842
- if (!existsSync3(dir)) {
29843
- mkdirSync(dir, { recursive: true });
30697
+ if (!existsSync5(dir)) {
30698
+ mkdirSync2(dir, { recursive: true });
29844
30699
  }
29845
30700
  }
29846
30701
  }
@@ -29977,6 +30832,20 @@ class EmbeddedClient {
29977
30832
  getSessionId() {
29978
30833
  return this.session.getSessionId();
29979
30834
  }
30835
+ async getCommands() {
30836
+ if (!this.initialized) {
30837
+ await this.initialize();
30838
+ }
30839
+ return this.agent.getCommands();
30840
+ }
30841
+ getTokenUsage() {
30842
+ return this.agent.getTokenUsage();
30843
+ }
30844
+ clearConversation() {
30845
+ this.agent.clearConversation();
30846
+ this.messages = [];
30847
+ this.logger.info("Conversation cleared");
30848
+ }
29980
30849
  }
29981
30850
  // packages/terminal/src/components/Input.tsx
29982
30851
  var import_react23 = __toESM(require_react(), 1);
@@ -30158,7 +31027,7 @@ function parseMarkdown(text) {
30158
31027
 
30159
31028
  // packages/terminal/src/components/Messages.tsx
30160
31029
  var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
30161
- function Messages4({ messages, currentResponse, currentToolCall, lastToolResult }) {
31030
+ function Messages4({ messages, currentResponse, currentToolCall, lastToolResult, activityLog = [] }) {
30162
31031
  const visibleMessages = messages.slice(-15);
30163
31032
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
30164
31033
  flexDirection: "column",
@@ -30166,7 +31035,37 @@ function Messages4({ messages, currentResponse, currentToolCall, lastToolResult
30166
31035
  visibleMessages.map((message) => /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(MessageBubble, {
30167
31036
  message
30168
31037
  }, message.id, false, undefined, this)),
30169
- currentToolCall && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31038
+ activityLog.map((entry) => {
31039
+ if (entry.type === "tool_call" && entry.toolCall) {
31040
+ return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31041
+ marginY: 1,
31042
+ children: [
31043
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
31044
+ dimColor: true,
31045
+ children: "\u25D0 "
31046
+ }, undefined, false, undefined, this),
31047
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
31048
+ dimColor: true,
31049
+ children: formatToolCall(entry.toolCall)
31050
+ }, undefined, false, undefined, this)
31051
+ ]
31052
+ }, entry.id, true, undefined, this);
31053
+ }
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
+ return null;
31067
+ }),
31068
+ currentToolCall && !activityLog.some((e) => e.toolCall?.id === currentToolCall.id) && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
30170
31069
  marginY: 1,
30171
31070
  children: [
30172
31071
  /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
@@ -30179,7 +31078,7 @@ function Messages4({ messages, currentResponse, currentToolCall, lastToolResult
30179
31078
  }, undefined, false, undefined, this)
30180
31079
  ]
30181
31080
  }, undefined, true, undefined, this),
30182
- lastToolResult && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31081
+ lastToolResult && !activityLog.some((e) => e.toolResult?.toolCallId === lastToolResult.toolCallId) && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
30183
31082
  marginY: 1,
30184
31083
  marginLeft: 2,
30185
31084
  children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
@@ -30297,10 +31196,17 @@ function truncate(text, maxLength) {
30297
31196
 
30298
31197
  // packages/terminal/src/components/Status.tsx
30299
31198
  var jsx_dev_runtime4 = __toESM(require_jsx_dev_runtime(), 1);
30300
- function Status({ isProcessing, cwd: cwd2, queueLength = 0 }) {
30301
- const maxCwdLength = 50;
31199
+ function Status({ isProcessing, cwd: cwd2, queueLength = 0, tokenUsage }) {
31200
+ const maxCwdLength = 30;
30302
31201
  const displayCwd = cwd2.length > maxCwdLength ? "..." + cwd2.slice(-(maxCwdLength - 3)) : cwd2;
30303
31202
  const queueInfo = queueLength > 0 ? ` | ${queueLength} queued` : "";
31203
+ let tokenInfo = "";
31204
+ if (tokenUsage && tokenUsage.totalTokens > 0) {
31205
+ const used = Math.round(tokenUsage.totalTokens / 1000);
31206
+ const max = Math.round(tokenUsage.maxContextTokens / 1000);
31207
+ const percent = Math.round(tokenUsage.totalTokens / tokenUsage.maxContextTokens * 100);
31208
+ tokenInfo = ` | ${used}k/${max}k (${percent}%)`;
31209
+ }
30304
31210
  return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
30305
31211
  marginTop: 1,
30306
31212
  borderStyle: "single",
@@ -30318,6 +31224,10 @@ function Status({ isProcessing, cwd: cwd2, queueLength = 0 }) {
30318
31224
  dimColor: !isProcessing,
30319
31225
  children: isProcessing ? "\u25CF processing" : "\u25CF ready"
30320
31226
  }, undefined, false, undefined, this),
31227
+ /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
31228
+ dimColor: true,
31229
+ children: tokenInfo
31230
+ }, undefined, false, undefined, this),
30321
31231
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
30322
31232
  dimColor: true,
30323
31233
  children: queueInfo
@@ -30326,7 +31236,8 @@ function Status({ isProcessing, cwd: cwd2, queueLength = 0 }) {
30326
31236
  dimColor: true,
30327
31237
  children: [
30328
31238
  " | ",
30329
- isProcessing ? "Esc to stop" : "Ctrl+C to exit"
31239
+ isProcessing ? "Esc to stop" : "Ctrl+C to exit",
31240
+ " | /help"
30330
31241
  ]
30331
31242
  }, undefined, true, undefined, this)
30332
31243
  ]
@@ -30391,8 +31302,12 @@ function App2({ cwd: cwd2 }) {
30391
31302
  const [isInitializing, setIsInitializing] = import_react25.useState(true);
30392
31303
  const [error, setError] = import_react25.useState(null);
30393
31304
  const [messageQueue, setMessageQueue] = import_react25.useState([]);
31305
+ const [activityLog, setActivityLog] = import_react25.useState([]);
31306
+ const [tokenUsage, setTokenUsage] = import_react25.useState();
30394
31307
  const responseRef = import_react25.useRef("");
30395
31308
  const clientRef = import_react25.useRef(null);
31309
+ const toolCallsRef = import_react25.useRef([]);
31310
+ const toolResultsRef = import_react25.useRef([]);
30396
31311
  const processQueue = import_react25.useCallback(async () => {
30397
31312
  if (!clientRef.current || messageQueue.length === 0)
30398
31313
  return;
@@ -30407,9 +31322,12 @@ function App2({ cwd: cwd2 }) {
30407
31322
  setMessages((prev) => [...prev, userMessage]);
30408
31323
  setCurrentResponse("");
30409
31324
  responseRef.current = "";
31325
+ toolCallsRef.current = [];
31326
+ toolResultsRef.current = [];
30410
31327
  setError(null);
30411
31328
  setCurrentToolCall(undefined);
30412
31329
  setLastToolResult(undefined);
31330
+ setActivityLog([]);
30413
31331
  setIsProcessing(true);
30414
31332
  await clientRef.current.send(nextMessage);
30415
31333
  }, [messageQueue]);
@@ -30423,31 +31341,60 @@ function App2({ cwd: cwd2 }) {
30423
31341
  responseRef.current += chunk.content;
30424
31342
  setCurrentResponse(responseRef.current);
30425
31343
  } else if (chunk.type === "tool_use" && chunk.toolCall) {
31344
+ toolCallsRef.current.push(chunk.toolCall);
31345
+ setActivityLog((prev) => [
31346
+ ...prev,
31347
+ {
31348
+ id: generateId(),
31349
+ type: "tool_call",
31350
+ toolCall: chunk.toolCall,
31351
+ timestamp: now()
31352
+ }
31353
+ ]);
30426
31354
  setCurrentToolCall(chunk.toolCall);
30427
31355
  setLastToolResult(undefined);
30428
31356
  } else if (chunk.type === "tool_result" && chunk.toolResult) {
31357
+ toolResultsRef.current.push(chunk.toolResult);
31358
+ setActivityLog((prev) => [
31359
+ ...prev,
31360
+ {
31361
+ id: generateId(),
31362
+ type: "tool_result",
31363
+ toolResult: chunk.toolResult,
31364
+ timestamp: now()
31365
+ }
31366
+ ]);
30429
31367
  setLastToolResult(chunk.toolResult);
30430
31368
  setCurrentToolCall(undefined);
30431
31369
  } else if (chunk.type === "error" && chunk.error) {
30432
31370
  setError(chunk.error);
30433
31371
  setIsProcessing(false);
31372
+ } else if (chunk.type === "usage" && chunk.usage) {
31373
+ setTokenUsage(chunk.usage);
30434
31374
  } else if (chunk.type === "done") {
30435
- if (responseRef.current) {
31375
+ if (responseRef.current || toolCallsRef.current.length > 0) {
30436
31376
  setMessages((prev) => [
30437
31377
  ...prev,
30438
31378
  {
30439
31379
  id: generateId(),
30440
31380
  role: "assistant",
30441
31381
  content: responseRef.current,
30442
- timestamp: now()
31382
+ timestamp: now(),
31383
+ toolCalls: toolCallsRef.current.length > 0 ? [...toolCallsRef.current] : undefined,
31384
+ toolResults: toolResultsRef.current.length > 0 ? [...toolResultsRef.current] : undefined
30443
31385
  }
30444
31386
  ]);
30445
31387
  setCurrentResponse("");
30446
31388
  responseRef.current = "";
31389
+ toolCallsRef.current = [];
31390
+ toolResultsRef.current = [];
30447
31391
  }
30448
31392
  setCurrentToolCall(undefined);
30449
31393
  setLastToolResult(undefined);
30450
31394
  setIsProcessing(false);
31395
+ if (newClient) {
31396
+ setTokenUsage(newClient.getTokenUsage());
31397
+ }
30451
31398
  }
30452
31399
  });
30453
31400
  newClient.onError((err) => {
@@ -30550,9 +31497,12 @@ function App2({ cwd: cwd2 }) {
30550
31497
  setMessages((prev) => [...prev, userMessage]);
30551
31498
  setCurrentResponse("");
30552
31499
  responseRef.current = "";
31500
+ toolCallsRef.current = [];
31501
+ toolResultsRef.current = [];
30553
31502
  setError(null);
30554
31503
  setCurrentToolCall(undefined);
30555
31504
  setLastToolResult(undefined);
31505
+ setActivityLog([]);
30556
31506
  setIsProcessing(true);
30557
31507
  await client.send(trimmedInput);
30558
31508
  }, [client, isProcessing]);
@@ -30573,7 +31523,8 @@ function App2({ cwd: cwd2 }) {
30573
31523
  messages,
30574
31524
  currentResponse: isProcessing ? currentResponse : undefined,
30575
31525
  currentToolCall,
30576
- lastToolResult
31526
+ lastToolResult,
31527
+ activityLog: isProcessing ? activityLog : []
30577
31528
  }, undefined, false, undefined, this),
30578
31529
  messageQueue.length > 0 && /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
30579
31530
  marginY: 1,
@@ -30611,7 +31562,8 @@ function App2({ cwd: cwd2 }) {
30611
31562
  /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Status, {
30612
31563
  isProcessing,
30613
31564
  cwd: cwd2,
30614
- queueLength: messageQueue.length
31565
+ queueLength: messageQueue.length,
31566
+ tokenUsage
30615
31567
  }, undefined, false, undefined, this)
30616
31568
  ]
30617
31569
  }, undefined, true, undefined, this);
@@ -30626,7 +31578,7 @@ var options = {
30626
31578
  help: args.includes("--help") || args.includes("-h")
30627
31579
  };
30628
31580
  if (options.version) {
30629
- console.log("oldpal v0.1.7");
31581
+ console.log("oldpal v0.1.9");
30630
31582
  process.exit(0);
30631
31583
  }
30632
31584
  if (options.help) {
@@ -30657,4 +31609,4 @@ waitUntilExit().then(() => {
30657
31609
  process.exit(0);
30658
31610
  });
30659
31611
 
30660
- //# debugId=F204A4187B2BDE8664756E2164756E21
31612
+ //# debugId=307434E7D5151E5864756E2164756E21