@hasna/oldpal 0.1.8 → 0.2.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 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]+)["']?/);
@@ -29368,10 +29368,101 @@ function parseDuckDuckGoResults(html, maxResults) {
29368
29368
  return results;
29369
29369
  }
29370
29370
 
29371
+ class CurlTool {
29372
+ static tool = {
29373
+ name: "curl",
29374
+ description: "Fetch content from a URL (like curl). Returns text content from web pages, JSON from APIs, etc.",
29375
+ parameters: {
29376
+ type: "object",
29377
+ properties: {
29378
+ url: {
29379
+ type: "string",
29380
+ description: "The URL to fetch"
29381
+ },
29382
+ method: {
29383
+ type: "string",
29384
+ description: "HTTP method (GET, POST, PUT, DELETE). Defaults to GET.",
29385
+ enum: ["GET", "POST", "PUT", "DELETE"],
29386
+ default: "GET"
29387
+ },
29388
+ headers: {
29389
+ type: "object",
29390
+ description: "Optional headers to send with the request"
29391
+ },
29392
+ body: {
29393
+ type: "string",
29394
+ description: "Request body for POST/PUT requests"
29395
+ }
29396
+ },
29397
+ required: ["url"]
29398
+ }
29399
+ };
29400
+ static executor = async (input) => {
29401
+ const url = input.url;
29402
+ const method = input.method || "GET";
29403
+ const headers = input.headers || {};
29404
+ const body = input.body;
29405
+ const timeout = 30000;
29406
+ try {
29407
+ const parsedUrl = new URL(url);
29408
+ const hostname = parsedUrl.hostname;
29409
+ if (hostname === "localhost" || hostname === "127.0.0.1" || hostname.startsWith("192.168.") || hostname.startsWith("10.") || hostname.startsWith("172.")) {
29410
+ return "Error: Cannot fetch from local/private network addresses for security reasons";
29411
+ }
29412
+ const controller = new AbortController;
29413
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
29414
+ const response = await fetch(url, {
29415
+ method,
29416
+ signal: controller.signal,
29417
+ headers: {
29418
+ "User-Agent": "oldpal/1.0 (AI Assistant)",
29419
+ ...headers
29420
+ },
29421
+ body: body && ["POST", "PUT"].includes(method) ? body : undefined
29422
+ });
29423
+ clearTimeout(timeoutId);
29424
+ const contentType = response.headers.get("content-type") || "";
29425
+ let responseBody;
29426
+ if (contentType.includes("application/json")) {
29427
+ try {
29428
+ const json = await response.json();
29429
+ responseBody = JSON.stringify(json, null, 2);
29430
+ } catch {
29431
+ responseBody = await response.text();
29432
+ }
29433
+ } else {
29434
+ responseBody = await response.text();
29435
+ if (contentType.includes("text/html")) {
29436
+ responseBody = extractReadableText(responseBody);
29437
+ }
29438
+ }
29439
+ const maxLength = 30000;
29440
+ if (responseBody.length > maxLength) {
29441
+ responseBody = responseBody.slice(0, maxLength) + `
29442
+
29443
+ [Content truncated...]`;
29444
+ }
29445
+ const statusLine = `HTTP ${response.status} ${response.statusText}`;
29446
+ return `${statusLine}
29447
+
29448
+ ${responseBody || "(empty response)"}`;
29449
+ } catch (error) {
29450
+ if (error instanceof Error) {
29451
+ if (error.name === "AbortError") {
29452
+ return `Error: Request timed out after ${timeout}ms`;
29453
+ }
29454
+ return `Error: ${error.message}`;
29455
+ }
29456
+ return `Error: ${String(error)}`;
29457
+ }
29458
+ };
29459
+ }
29460
+
29371
29461
  class WebTools {
29372
29462
  static registerAll(registry) {
29373
29463
  registry.register(WebFetchTool.tool, WebFetchTool.executor);
29374
29464
  registry.register(WebSearchTool.tool, WebSearchTool.executor);
29465
+ registry.register(CurlTool.tool, CurlTool.executor);
29375
29466
  }
29376
29467
  }
29377
29468
 
@@ -29643,7 +29734,609 @@ class HookExecutor {
29643
29734
  return null;
29644
29735
  }
29645
29736
  }
29737
+ // packages/core/src/commands/loader.ts
29738
+ import { existsSync as existsSync2, readdirSync, statSync } from "fs";
29739
+ import { join as join4, basename, extname } from "path";
29740
+ import { homedir as homedir3 } from "os";
29741
+
29742
+ class CommandLoader {
29743
+ commands = new Map;
29744
+ cwd;
29745
+ constructor(cwd2) {
29746
+ this.cwd = cwd2 || process.cwd();
29747
+ }
29748
+ async loadAll() {
29749
+ this.commands.clear();
29750
+ const globalDir = join4(homedir3(), ".oldpal", "commands");
29751
+ await this.loadFromDirectory(globalDir, "global");
29752
+ const projectDir = join4(this.cwd, ".oldpal", "commands");
29753
+ await this.loadFromDirectory(projectDir, "project");
29754
+ }
29755
+ async loadFromDirectory(dir, source, prefix = "") {
29756
+ if (!existsSync2(dir))
29757
+ return;
29758
+ const entries = readdirSync(dir);
29759
+ for (const entry of entries) {
29760
+ const fullPath = join4(dir, entry);
29761
+ const stat = statSync(fullPath);
29762
+ if (stat.isDirectory()) {
29763
+ const newPrefix = prefix ? `${prefix}:${entry}` : entry;
29764
+ await this.loadFromDirectory(fullPath, source, newPrefix);
29765
+ } else if (stat.isFile() && extname(entry) === ".md") {
29766
+ const command = await this.loadCommandFile(fullPath, prefix);
29767
+ if (command) {
29768
+ this.commands.set(command.name, command);
29769
+ }
29770
+ }
29771
+ }
29772
+ }
29773
+ async loadCommandFile(filePath, prefix) {
29774
+ try {
29775
+ const content = await Bun.file(filePath).text();
29776
+ const { frontmatter, body } = this.parseFrontmatter(content);
29777
+ const fileName = basename(filePath, ".md");
29778
+ const name = frontmatter.name || (prefix ? `${prefix}:${fileName}` : fileName);
29779
+ return {
29780
+ name,
29781
+ description: frontmatter.description || `Run the ${name} command`,
29782
+ tags: frontmatter.tags,
29783
+ allowedTools: frontmatter["allowed-tools"]?.split(",").map((t) => t.trim()),
29784
+ content: body,
29785
+ filePath,
29786
+ builtin: false
29787
+ };
29788
+ } catch (error) {
29789
+ console.error(`Failed to load command from ${filePath}:`, error);
29790
+ return null;
29791
+ }
29792
+ }
29793
+ parseFrontmatter(content) {
29794
+ const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
29795
+ const match = content.match(frontmatterRegex);
29796
+ if (!match) {
29797
+ return { frontmatter: {}, body: content };
29798
+ }
29799
+ const [, yamlContent, body] = match;
29800
+ const frontmatter = {};
29801
+ const lines = yamlContent.split(`
29802
+ `);
29803
+ for (const line of lines) {
29804
+ const colonIndex = line.indexOf(":");
29805
+ if (colonIndex === -1)
29806
+ continue;
29807
+ const key = line.slice(0, colonIndex).trim();
29808
+ let value = line.slice(colonIndex + 1).trim();
29809
+ if (value.startsWith("[") && value.endsWith("]")) {
29810
+ const arrayContent = value.slice(1, -1);
29811
+ frontmatter[key] = arrayContent.split(",").map((v) => v.trim());
29812
+ } else {
29813
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
29814
+ value = value.slice(1, -1);
29815
+ }
29816
+ frontmatter[key] = value;
29817
+ }
29818
+ }
29819
+ return { frontmatter, body: body.trim() };
29820
+ }
29821
+ register(command) {
29822
+ this.commands.set(command.name, command);
29823
+ }
29824
+ getCommand(name) {
29825
+ return this.commands.get(name);
29826
+ }
29827
+ getCommands() {
29828
+ return Array.from(this.commands.values());
29829
+ }
29830
+ hasCommand(name) {
29831
+ return this.commands.has(name);
29832
+ }
29833
+ findMatching(partial) {
29834
+ const lower = partial.toLowerCase();
29835
+ return this.getCommands().filter((cmd) => cmd.name.toLowerCase().startsWith(lower) || cmd.description.toLowerCase().includes(lower));
29836
+ }
29837
+ }
29838
+ // packages/core/src/commands/executor.ts
29839
+ class CommandExecutor {
29840
+ loader;
29841
+ constructor(loader) {
29842
+ this.loader = loader;
29843
+ }
29844
+ parseCommand(input) {
29845
+ const trimmed = input.trim();
29846
+ if (!trimmed.startsWith("/")) {
29847
+ return null;
29848
+ }
29849
+ const match = trimmed.match(/^\/(\S+)(?:\s+(.*))?$/);
29850
+ if (!match) {
29851
+ return null;
29852
+ }
29853
+ return {
29854
+ name: match[1],
29855
+ args: match[2] || ""
29856
+ };
29857
+ }
29858
+ isCommand(input) {
29859
+ return this.parseCommand(input) !== null;
29860
+ }
29861
+ async execute(input, context) {
29862
+ const parsed = this.parseCommand(input);
29863
+ if (!parsed) {
29864
+ return { handled: false };
29865
+ }
29866
+ const command = this.loader.getCommand(parsed.name);
29867
+ if (!command) {
29868
+ context.emit("text", `Unknown command: /${parsed.name}
29869
+
29870
+ Use /help to see available commands.
29871
+ `);
29872
+ context.emit("done");
29873
+ return { handled: true };
29874
+ }
29875
+ if (command.selfHandled && command.handler) {
29876
+ return command.handler(parsed.args, context);
29877
+ }
29878
+ const prompt = await this.preparePrompt(command, parsed.args);
29879
+ return {
29880
+ handled: false,
29881
+ prompt
29882
+ };
29883
+ }
29884
+ async preparePrompt(command, args) {
29885
+ let content = command.content;
29886
+ content = content.replace(/\$ARGUMENTS/g, args || "(no arguments provided)");
29887
+ content = await this.processShellCommands(content);
29888
+ return content;
29889
+ }
29890
+ async processShellCommands(content) {
29891
+ const lines = content.split(`
29892
+ `);
29893
+ const processedLines = [];
29894
+ for (const line of lines) {
29895
+ const trimmed = line.trim();
29896
+ if (trimmed.startsWith("!")) {
29897
+ const command = trimmed.slice(1).trim();
29898
+ const output = await this.executeShell(command);
29899
+ processedLines.push(`\`\`\`
29900
+ ${output}
29901
+ \`\`\``);
29902
+ } else {
29903
+ processedLines.push(line);
29904
+ }
29905
+ }
29906
+ return processedLines.join(`
29907
+ `);
29908
+ }
29909
+ async executeShell(command) {
29910
+ try {
29911
+ const proc = Bun.spawn(["bash", "-c", command], {
29912
+ stdout: "pipe",
29913
+ stderr: "pipe"
29914
+ });
29915
+ const [stdout, stderr] = await Promise.all([
29916
+ new Response(proc.stdout).text(),
29917
+ new Response(proc.stderr).text()
29918
+ ]);
29919
+ const exitCode = await proc.exited;
29920
+ if (exitCode !== 0 && stderr) {
29921
+ return `Error (exit ${exitCode}):
29922
+ ${stderr}`;
29923
+ }
29924
+ return stdout.trim() || "(no output)";
29925
+ } catch (error) {
29926
+ return `Error: ${error instanceof Error ? error.message : String(error)}`;
29927
+ }
29928
+ }
29929
+ getSuggestions(partial) {
29930
+ if (!partial.startsWith("/")) {
29931
+ return [];
29932
+ }
29933
+ const name = partial.slice(1).toLowerCase();
29934
+ return this.loader.findMatching(name);
29935
+ }
29936
+ }
29937
+ // packages/core/src/commands/builtin.ts
29938
+ import { join as join5 } from "path";
29939
+ import { homedir as homedir4 } from "os";
29940
+ import { existsSync as existsSync3, mkdirSync, writeFileSync } from "fs";
29941
+
29942
+ class BuiltinCommands {
29943
+ tokenUsage = {
29944
+ inputTokens: 0,
29945
+ outputTokens: 0,
29946
+ totalTokens: 0,
29947
+ maxContextTokens: 200000
29948
+ };
29949
+ registerAll(loader) {
29950
+ loader.register(this.helpCommand(loader));
29951
+ loader.register(this.clearCommand());
29952
+ loader.register(this.statusCommand());
29953
+ loader.register(this.compactCommand());
29954
+ loader.register(this.configCommand());
29955
+ loader.register(this.initCommand());
29956
+ loader.register(this.costCommand());
29957
+ loader.register(this.modelCommand());
29958
+ loader.register(this.memoryCommand());
29959
+ loader.register(this.bugCommand());
29960
+ loader.register(this.prCommand());
29961
+ loader.register(this.reviewCommand());
29962
+ }
29963
+ updateTokenUsage(usage) {
29964
+ Object.assign(this.tokenUsage, usage);
29965
+ }
29966
+ getTokenUsage() {
29967
+ return { ...this.tokenUsage };
29968
+ }
29969
+ helpCommand(loader) {
29970
+ return {
29971
+ name: "help",
29972
+ description: "Show available slash commands",
29973
+ builtin: true,
29974
+ selfHandled: true,
29975
+ content: "",
29976
+ handler: async (args, context) => {
29977
+ const commands = loader.getCommands();
29978
+ const builtinCmds = commands.filter((c) => c.builtin);
29979
+ const customCmds = commands.filter((c) => !c.builtin);
29980
+ let message = `
29981
+ **Available Slash Commands**
29982
+
29983
+ `;
29984
+ if (builtinCmds.length > 0) {
29985
+ message += `**Built-in Commands:**
29986
+ `;
29987
+ for (const cmd of builtinCmds.sort((a, b) => a.name.localeCompare(b.name))) {
29988
+ message += ` /${cmd.name} - ${cmd.description}
29989
+ `;
29990
+ }
29991
+ message += `
29992
+ `;
29993
+ }
29994
+ if (customCmds.length > 0) {
29995
+ message += `**Custom Commands:**
29996
+ `;
29997
+ for (const cmd of customCmds.sort((a, b) => a.name.localeCompare(b.name))) {
29998
+ message += ` /${cmd.name} - ${cmd.description}
29999
+ `;
30000
+ }
30001
+ message += `
30002
+ `;
30003
+ }
30004
+ message += `**Tips:**
30005
+ `;
30006
+ message += ` - Create custom commands in .oldpal/commands/*.md
30007
+ `;
30008
+ message += ` - Global commands go in ~/.oldpal/commands/*.md
30009
+ `;
30010
+ message += ` - Use /init to create a starter command
30011
+ `;
30012
+ context.emit("text", message);
30013
+ context.emit("done");
30014
+ return { handled: true };
30015
+ }
30016
+ };
30017
+ }
30018
+ clearCommand() {
30019
+ return {
30020
+ name: "clear",
30021
+ description: "Clear conversation history and start fresh",
30022
+ builtin: true,
30023
+ selfHandled: true,
30024
+ content: "",
30025
+ handler: async (args, context) => {
30026
+ context.clearMessages();
30027
+ this.tokenUsage.inputTokens = 0;
30028
+ this.tokenUsage.outputTokens = 0;
30029
+ this.tokenUsage.totalTokens = 0;
30030
+ context.emit("text", `Conversation cleared. Starting fresh.
30031
+ `);
30032
+ context.emit("done");
30033
+ return { handled: true, clearConversation: true };
30034
+ }
30035
+ };
30036
+ }
30037
+ statusCommand() {
30038
+ return {
30039
+ name: "status",
30040
+ description: "Show current session status and token usage",
30041
+ builtin: true,
30042
+ selfHandled: true,
30043
+ content: "",
30044
+ handler: async (args, context) => {
30045
+ const usage = this.tokenUsage;
30046
+ const usedPercent = Math.round(usage.totalTokens / usage.maxContextTokens * 100);
30047
+ let message = `
30048
+ **Session Status**
30049
+
30050
+ `;
30051
+ message += `**Working Directory:** ${context.cwd}
30052
+ `;
30053
+ message += `**Session ID:** ${context.sessionId}
30054
+ `;
30055
+ message += `**Messages:** ${context.messages.length}
30056
+ `;
30057
+ message += `**Available Tools:** ${context.tools.length}
30058
+
30059
+ `;
30060
+ message += `**Token Usage:**
30061
+ `;
30062
+ message += ` Input: ${usage.inputTokens.toLocaleString()}
30063
+ `;
30064
+ message += ` Output: ${usage.outputTokens.toLocaleString()}
30065
+ `;
30066
+ message += ` Total: ${usage.totalTokens.toLocaleString()} / ${usage.maxContextTokens.toLocaleString()} (${usedPercent}%)
30067
+ `;
30068
+ if (usage.cacheReadTokens || usage.cacheWriteTokens) {
30069
+ message += ` Cache Read: ${(usage.cacheReadTokens || 0).toLocaleString()}
30070
+ `;
30071
+ message += ` Cache Write: ${(usage.cacheWriteTokens || 0).toLocaleString()}
30072
+ `;
30073
+ }
30074
+ const barLength = 30;
30075
+ const filledLength = Math.round(usedPercent / 100 * barLength);
30076
+ const bar = "\u2588".repeat(filledLength) + "\u2591".repeat(barLength - filledLength);
30077
+ message += `
30078
+ [${bar}] ${usedPercent}%
30079
+ `;
30080
+ context.emit("text", message);
30081
+ context.emit("done");
30082
+ return { handled: true };
30083
+ }
30084
+ };
30085
+ }
30086
+ compactCommand() {
30087
+ return {
30088
+ name: "compact",
30089
+ description: "Summarize conversation to save context space",
30090
+ builtin: true,
30091
+ selfHandled: false,
30092
+ content: `Please summarize our conversation so far into a concise format that preserves:
30093
+ 1. Key decisions made
30094
+ 2. Important context about the codebase
30095
+ 3. Current task/goal we're working on
30096
+ 4. Any constraints or requirements mentioned
30097
+
30098
+ Format the summary as a brief bullet-point list. This summary will replace the conversation history to save context space.`
30099
+ };
30100
+ }
30101
+ configCommand() {
30102
+ return {
30103
+ name: "config",
30104
+ description: "Show current configuration",
30105
+ builtin: true,
30106
+ selfHandled: true,
30107
+ content: "",
30108
+ handler: async (args, context) => {
30109
+ const configPaths = [
30110
+ join5(context.cwd, ".oldpal", "config.json"),
30111
+ join5(homedir4(), ".oldpal", "config.json")
30112
+ ];
30113
+ let message = `
30114
+ **Configuration**
30115
+
30116
+ `;
30117
+ message += `**Config File Locations:**
30118
+ `;
30119
+ for (const path of configPaths) {
30120
+ const exists = existsSync3(path);
30121
+ message += ` ${exists ? "\u2713" : "\u25CB"} ${path}
30122
+ `;
30123
+ }
30124
+ message += `
30125
+ **Commands Directories:**
30126
+ `;
30127
+ message += ` - Project: ${join5(context.cwd, ".oldpal", "commands")}
30128
+ `;
30129
+ message += ` - Global: ${join5(homedir4(), ".oldpal", "commands")}
30130
+ `;
30131
+ context.emit("text", message);
30132
+ context.emit("done");
30133
+ return { handled: true };
30134
+ }
30135
+ };
30136
+ }
30137
+ initCommand() {
30138
+ return {
30139
+ name: "init",
30140
+ description: "Initialize oldpal config and create example command",
30141
+ builtin: true,
30142
+ selfHandled: true,
30143
+ content: "",
30144
+ handler: async (args, context) => {
30145
+ const commandsDir = join5(context.cwd, ".oldpal", "commands");
30146
+ mkdirSync(commandsDir, { recursive: true });
30147
+ const exampleCommand = `---
30148
+ name: review
30149
+ description: Review code changes for issues and improvements
30150
+ tags: [code, review]
30151
+ ---
30152
+
30153
+ # Code Review
30154
+
30155
+ Please review the current code changes and provide feedback on:
30156
+
30157
+ 1. **Code Quality**
30158
+ - Readability and maintainability
30159
+ - Following project conventions
30160
+ - Proper error handling
30161
+
30162
+ 2. **Potential Issues**
30163
+ - Security vulnerabilities
30164
+ - Performance concerns
30165
+ - Edge cases not handled
30166
+
30167
+ 3. **Suggestions**
30168
+ - Improvements to consider
30169
+ - Best practices to apply
30170
+ - Documentation needs
30171
+
30172
+ If there are staged git changes, focus on those. Otherwise, ask what code to review.
30173
+ `;
30174
+ const examplePath = join5(commandsDir, "review.md");
30175
+ if (!existsSync3(examplePath)) {
30176
+ writeFileSync(examplePath, exampleCommand);
30177
+ }
30178
+ let message = `
30179
+ **Initialized oldpal**
30180
+
30181
+ `;
30182
+ message += `Created: ${commandsDir}
30183
+ `;
30184
+ message += `Example: ${examplePath}
29646
30185
 
30186
+ `;
30187
+ message += `You can now:
30188
+ `;
30189
+ message += ` - Add custom commands to .oldpal/commands/
30190
+ `;
30191
+ message += ` - Use /review to try the example command
30192
+ `;
30193
+ message += ` - Run /help to see all available commands
30194
+ `;
30195
+ context.emit("text", message);
30196
+ context.emit("done");
30197
+ return { handled: true };
30198
+ }
30199
+ };
30200
+ }
30201
+ costCommand() {
30202
+ return {
30203
+ name: "cost",
30204
+ description: "Show estimated API cost for this session",
30205
+ builtin: true,
30206
+ selfHandled: true,
30207
+ content: "",
30208
+ handler: async (args, context) => {
30209
+ const usage = this.tokenUsage;
30210
+ const inputCostPer1M = 3;
30211
+ const outputCostPer1M = 15;
30212
+ const inputCost = usage.inputTokens / 1e6 * inputCostPer1M;
30213
+ const outputCost = usage.outputTokens / 1e6 * outputCostPer1M;
30214
+ const totalCost = inputCost + outputCost;
30215
+ const cacheReadCostPer1M = 0.3;
30216
+ const cacheSavings = usage.cacheReadTokens ? usage.cacheReadTokens / 1e6 * (inputCostPer1M - cacheReadCostPer1M) : 0;
30217
+ let message = `
30218
+ **Estimated Session Cost**
30219
+
30220
+ `;
30221
+ message += `Input tokens: ${usage.inputTokens.toLocaleString()} (~$${inputCost.toFixed(4)})
30222
+ `;
30223
+ message += `Output tokens: ${usage.outputTokens.toLocaleString()} (~$${outputCost.toFixed(4)})
30224
+ `;
30225
+ message += `**Total: ~$${totalCost.toFixed(4)}**
30226
+ `;
30227
+ if (cacheSavings > 0) {
30228
+ message += `
30229
+ Cache savings: ~$${cacheSavings.toFixed(4)}
30230
+ `;
30231
+ }
30232
+ message += `
30233
+ *Based on Claude 3.5 Sonnet pricing*
30234
+ `;
30235
+ context.emit("text", message);
30236
+ context.emit("done");
30237
+ return { handled: true };
30238
+ }
30239
+ };
30240
+ }
30241
+ modelCommand() {
30242
+ return {
30243
+ name: "model",
30244
+ description: "Show current model information",
30245
+ builtin: true,
30246
+ selfHandled: true,
30247
+ content: "",
30248
+ handler: async (args, context) => {
30249
+ let message = `
30250
+ **Model Information**
30251
+
30252
+ `;
30253
+ message += `Current model: claude-3-5-sonnet-20241022
30254
+ `;
30255
+ message += `Context window: 200,000 tokens
30256
+ `;
30257
+ message += `Max output: 8,192 tokens
30258
+
30259
+ `;
30260
+ message += `*Model selection coming in a future update*
30261
+ `;
30262
+ context.emit("text", message);
30263
+ context.emit("done");
30264
+ return { handled: true };
30265
+ }
30266
+ };
30267
+ }
30268
+ memoryCommand() {
30269
+ return {
30270
+ name: "memory",
30271
+ description: "Show conversation summary and key memories",
30272
+ builtin: true,
30273
+ selfHandled: false,
30274
+ content: `Please provide a summary of our conversation so far, including:
30275
+
30276
+ 1. **Key Context** - What you know about this project/codebase
30277
+ 2. **Current Task** - What we're working on
30278
+ 3. **Decisions Made** - Any choices or agreements from our discussion
30279
+ 4. **Open Items** - Things we mentioned but haven't addressed yet
30280
+
30281
+ Keep it concise but comprehensive.`
30282
+ };
30283
+ }
30284
+ bugCommand() {
30285
+ return {
30286
+ name: "bug",
30287
+ description: "Analyze and help fix a bug",
30288
+ builtin: true,
30289
+ selfHandled: false,
30290
+ content: `Help me debug an issue. $ARGUMENTS
30291
+
30292
+ Please:
30293
+ 1. Understand the bug/error described
30294
+ 2. Identify likely causes
30295
+ 3. Search relevant code files
30296
+ 4. Propose a fix with code changes
30297
+
30298
+ If no bug is described, ask me to describe the issue I'm experiencing.`
30299
+ };
30300
+ }
30301
+ prCommand() {
30302
+ return {
30303
+ name: "pr",
30304
+ description: "Create a pull request for current changes",
30305
+ builtin: true,
30306
+ selfHandled: false,
30307
+ content: `Help me create a pull request for the current changes.
30308
+
30309
+ 1. First, check git status and staged changes
30310
+ 2. Review the diff to understand what changed
30311
+ 3. Write a clear PR title (max 72 chars)
30312
+ 4. Write a description with:
30313
+ - Summary of changes
30314
+ - Motivation/context
30315
+ - Testing done
30316
+ - Any notes for reviewers
30317
+
30318
+ Then create the PR using the gh CLI.`
30319
+ };
30320
+ }
30321
+ reviewCommand() {
30322
+ return {
30323
+ name: "review",
30324
+ description: "Review code changes for issues",
30325
+ builtin: true,
30326
+ selfHandled: false,
30327
+ content: `Review the current code changes. $ARGUMENTS
30328
+
30329
+ Check for:
30330
+ 1. **Bugs** - Logic errors, edge cases, null checks
30331
+ 2. **Security** - Input validation, injection risks, secrets
30332
+ 3. **Performance** - N+1 queries, unnecessary loops, memory leaks
30333
+ 4. **Style** - Naming, formatting, code organization
30334
+ 5. **Tests** - Coverage, edge cases, assertions
30335
+
30336
+ If there are staged changes, review those. Otherwise, ask what to review.`
30337
+ };
30338
+ }
30339
+ }
29647
30340
  // packages/core/src/llm/client.ts
29648
30341
  async function createLLMClient(config) {
29649
30342
  if (config.provider === "anthropic") {
@@ -29654,8 +30347,8 @@ async function createLLMClient(config) {
29654
30347
  }
29655
30348
 
29656
30349
  // packages/core/src/config.ts
29657
- import { join as join5 } from "path";
29658
- import { homedir as homedir4 } from "os";
30350
+ import { join as join7 } from "path";
30351
+ import { homedir as homedir6 } from "os";
29659
30352
  var DEFAULT_CONFIG = {
29660
30353
  llm: {
29661
30354
  provider: "anthropic",
@@ -29685,13 +30378,13 @@ var DEFAULT_CONFIG = {
29685
30378
  ]
29686
30379
  };
29687
30380
  function getConfigDir() {
29688
- return join5(homedir4(), ".oldpal");
30381
+ return join7(homedir6(), ".oldpal");
29689
30382
  }
29690
30383
  function getConfigPath(filename) {
29691
- return join5(getConfigDir(), filename);
30384
+ return join7(getConfigDir(), filename);
29692
30385
  }
29693
30386
  function getProjectConfigDir(cwd2 = process.cwd()) {
29694
- return join5(cwd2, ".oldpal");
30387
+ return join7(cwd2, ".oldpal");
29695
30388
  }
29696
30389
  async function loadConfig(cwd2 = process.cwd()) {
29697
30390
  const config = { ...DEFAULT_CONFIG };
@@ -29704,7 +30397,7 @@ async function loadConfig(cwd2 = process.cwd()) {
29704
30397
  if (userConfig.voice)
29705
30398
  config.voice = { ...config.voice, ...userConfig.voice };
29706
30399
  }
29707
- const projectConfigPath = join5(getProjectConfigDir(cwd2), "settings.json");
30400
+ const projectConfigPath = join7(getProjectConfigDir(cwd2), "settings.json");
29708
30401
  const projectConfig = await loadJsonFile(projectConfigPath);
29709
30402
  if (projectConfig) {
29710
30403
  Object.assign(config, projectConfig);
@@ -29713,7 +30406,7 @@ async function loadConfig(cwd2 = process.cwd()) {
29713
30406
  if (projectConfig.voice)
29714
30407
  config.voice = { ...config.voice, ...projectConfig.voice };
29715
30408
  }
29716
- const localConfigPath = join5(getProjectConfigDir(cwd2), "settings.local.json");
30409
+ const localConfigPath = join7(getProjectConfigDir(cwd2), "settings.local.json");
29717
30410
  const localConfig = await loadJsonFile(localConfigPath);
29718
30411
  if (localConfig) {
29719
30412
  Object.assign(config, localConfig);
@@ -29731,7 +30424,7 @@ async function loadHooksConfig(cwd2 = process.cwd()) {
29731
30424
  if (userHooks?.hooks) {
29732
30425
  mergeHooks(hooks, userHooks.hooks);
29733
30426
  }
29734
- const projectHooksPath = join5(getProjectConfigDir(cwd2), "hooks.json");
30427
+ const projectHooksPath = join7(getProjectConfigDir(cwd2), "hooks.json");
29735
30428
  const projectHooks = await loadJsonFile(projectHooksPath);
29736
30429
  if (projectHooks?.hooks) {
29737
30430
  mergeHooks(hooks, projectHooks.hooks);
@@ -29767,16 +30460,22 @@ class AgentLoop {
29767
30460
  skillExecutor;
29768
30461
  hookLoader;
29769
30462
  hookExecutor;
30463
+ commandLoader;
30464
+ commandExecutor;
30465
+ builtinCommands;
29770
30466
  llmClient = null;
29771
30467
  config = null;
29772
30468
  cwd;
30469
+ sessionId;
29773
30470
  isRunning = false;
29774
30471
  shouldStop = false;
29775
30472
  onChunk;
29776
30473
  onToolStart;
29777
30474
  onToolEnd;
30475
+ onTokenUsage;
29778
30476
  constructor(options = {}) {
29779
30477
  this.cwd = options.cwd || process.cwd();
30478
+ this.sessionId = options.sessionId || generateId();
29780
30479
  this.context = new AgentContext;
29781
30480
  this.toolRegistry = new ToolRegistry;
29782
30481
  this.connectorBridge = new ConnectorBridge;
@@ -29784,9 +30483,13 @@ class AgentLoop {
29784
30483
  this.skillExecutor = new SkillExecutor;
29785
30484
  this.hookLoader = new HookLoader;
29786
30485
  this.hookExecutor = new HookExecutor;
30486
+ this.commandLoader = new CommandLoader(this.cwd);
30487
+ this.commandExecutor = new CommandExecutor(this.commandLoader);
30488
+ this.builtinCommands = new BuiltinCommands;
29787
30489
  this.onChunk = options.onChunk;
29788
30490
  this.onToolStart = options.onToolStart;
29789
30491
  this.onToolEnd = options.onToolEnd;
30492
+ this.onTokenUsage = options.onTokenUsage;
29790
30493
  }
29791
30494
  async initialize() {
29792
30495
  this.config = await loadConfig(this.cwd);
@@ -29797,10 +30500,12 @@ class AgentLoop {
29797
30500
  await this.connectorBridge.discover(this.config.connectors);
29798
30501
  this.connectorBridge.registerAll(this.toolRegistry);
29799
30502
  await this.skillLoader.loadAll(this.cwd);
30503
+ await this.commandLoader.loadAll();
30504
+ this.builtinCommands.registerAll(this.commandLoader);
29800
30505
  const hooksConfig = await loadHooksConfig(this.cwd);
29801
30506
  this.hookLoader.load(hooksConfig);
29802
30507
  await this.hookExecutor.execute(this.hookLoader.getHooks("SessionStart"), {
29803
- session_id: generateId(),
30508
+ session_id: this.sessionId,
29804
30509
  hook_event_name: "SessionStart",
29805
30510
  cwd: this.cwd
29806
30511
  });
@@ -29816,7 +30521,7 @@ class AgentLoop {
29816
30521
  this.shouldStop = false;
29817
30522
  try {
29818
30523
  const promptHookResult = await this.hookExecutor.execute(this.hookLoader.getHooks("UserPromptSubmit"), {
29819
- session_id: generateId(),
30524
+ session_id: this.sessionId,
29820
30525
  hook_event_name: "UserPromptSubmit",
29821
30526
  cwd: this.cwd,
29822
30527
  prompt: userMessage
@@ -29825,6 +30530,18 @@ class AgentLoop {
29825
30530
  this.emit({ type: "error", error: promptHookResult.stopReason || "Blocked by hook" });
29826
30531
  return;
29827
30532
  }
30533
+ if (userMessage.startsWith("/")) {
30534
+ const commandResult = await this.handleCommand(userMessage);
30535
+ if (commandResult.handled) {
30536
+ if (commandResult.clearConversation) {
30537
+ this.context = new AgentContext;
30538
+ }
30539
+ return;
30540
+ }
30541
+ if (commandResult.prompt) {
30542
+ userMessage = commandResult.prompt;
30543
+ }
30544
+ }
29828
30545
  if (userMessage.startsWith("/")) {
29829
30546
  const handled = await this.handleSkillInvocation(userMessage);
29830
30547
  if (handled)
@@ -29905,6 +30622,30 @@ class AgentLoop {
29905
30622
  }
29906
30623
  return results;
29907
30624
  }
30625
+ async handleCommand(message) {
30626
+ const context = {
30627
+ cwd: this.cwd,
30628
+ sessionId: this.sessionId,
30629
+ messages: this.context.getMessages(),
30630
+ tools: this.toolRegistry.getTools(),
30631
+ clearMessages: () => {
30632
+ this.context = new AgentContext;
30633
+ },
30634
+ addSystemMessage: (content) => {
30635
+ this.context.addSystemMessage(content);
30636
+ },
30637
+ emit: (type, content) => {
30638
+ if (type === "text" && content) {
30639
+ this.emit({ type: "text", content });
30640
+ } else if (type === "done") {
30641
+ this.emit({ type: "done" });
30642
+ } else if (type === "error" && content) {
30643
+ this.emit({ type: "error", error: content });
30644
+ }
30645
+ }
30646
+ };
30647
+ return this.commandExecutor.execute(message, context);
30648
+ }
29908
30649
  async handleSkillInvocation(message) {
29909
30650
  const match = message.match(/^\/(\S+)(?:\s+(.*))?$/);
29910
30651
  if (!match)
@@ -29936,18 +30677,34 @@ class AgentLoop {
29936
30677
  getSkills() {
29937
30678
  return this.skillLoader.getSkills();
29938
30679
  }
30680
+ getCommands() {
30681
+ return this.commandLoader.getCommands();
30682
+ }
30683
+ getTokenUsage() {
30684
+ return this.builtinCommands.getTokenUsage();
30685
+ }
30686
+ updateTokenUsage(usage) {
30687
+ this.builtinCommands.updateTokenUsage(usage);
30688
+ this.onTokenUsage?.(this.builtinCommands.getTokenUsage());
30689
+ }
29939
30690
  isProcessing() {
29940
30691
  return this.isRunning;
29941
30692
  }
30693
+ getSessionId() {
30694
+ return this.sessionId;
30695
+ }
30696
+ clearConversation() {
30697
+ this.context = new AgentContext;
30698
+ }
29942
30699
  }
29943
30700
 
29944
30701
  // packages/core/src/index.ts
29945
30702
  init_anthropic();
29946
30703
 
29947
30704
  // packages/core/src/logger.ts
29948
- import { existsSync as existsSync3, mkdirSync, appendFileSync } from "fs";
29949
- import { join as join6 } from "path";
29950
- import { homedir as homedir5 } from "os";
30705
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, appendFileSync } from "fs";
30706
+ import { join as join8 } from "path";
30707
+ import { homedir as homedir7 } from "os";
29951
30708
 
29952
30709
  class Logger {
29953
30710
  logDir;
@@ -29955,14 +30712,14 @@ class Logger {
29955
30712
  sessionId;
29956
30713
  constructor(sessionId) {
29957
30714
  this.sessionId = sessionId;
29958
- this.logDir = join6(homedir5(), ".oldpal", "logs");
30715
+ this.logDir = join8(homedir7(), ".oldpal", "logs");
29959
30716
  this.ensureDir(this.logDir);
29960
30717
  const date = new Date().toISOString().split("T")[0];
29961
- this.logFile = join6(this.logDir, `${date}.log`);
30718
+ this.logFile = join8(this.logDir, `${date}.log`);
29962
30719
  }
29963
30720
  ensureDir(dir) {
29964
- if (!existsSync3(dir)) {
29965
- mkdirSync(dir, { recursive: true });
30721
+ if (!existsSync5(dir)) {
30722
+ mkdirSync2(dir, { recursive: true });
29966
30723
  }
29967
30724
  }
29968
30725
  write(level, message, data) {
@@ -30001,13 +30758,13 @@ class SessionStorage {
30001
30758
  sessionId;
30002
30759
  constructor(sessionId) {
30003
30760
  this.sessionId = sessionId;
30004
- this.sessionsDir = join6(homedir5(), ".oldpal", "sessions");
30761
+ this.sessionsDir = join8(homedir7(), ".oldpal", "sessions");
30005
30762
  this.ensureDir(this.sessionsDir);
30006
- this.sessionFile = join6(this.sessionsDir, `${sessionId}.json`);
30763
+ this.sessionFile = join8(this.sessionsDir, `${sessionId}.json`);
30007
30764
  }
30008
30765
  ensureDir(dir) {
30009
- if (!existsSync3(dir)) {
30010
- mkdirSync(dir, { recursive: true });
30766
+ if (!existsSync5(dir)) {
30767
+ mkdirSync2(dir, { recursive: true });
30011
30768
  }
30012
30769
  }
30013
30770
  save(data) {
@@ -30020,16 +30777,16 @@ class SessionStorage {
30020
30777
  }
30021
30778
  }
30022
30779
  function initOldpalDir() {
30023
- const baseDir = join6(homedir5(), ".oldpal");
30780
+ const baseDir = join8(homedir7(), ".oldpal");
30024
30781
  const dirs = [
30025
30782
  baseDir,
30026
- join6(baseDir, "sessions"),
30027
- join6(baseDir, "logs"),
30028
- join6(baseDir, "skills")
30783
+ join8(baseDir, "sessions"),
30784
+ join8(baseDir, "logs"),
30785
+ join8(baseDir, "skills")
30029
30786
  ];
30030
30787
  for (const dir of dirs) {
30031
- if (!existsSync3(dir)) {
30032
- mkdirSync(dir, { recursive: true });
30788
+ if (!existsSync5(dir)) {
30789
+ mkdirSync2(dir, { recursive: true });
30033
30790
  }
30034
30791
  }
30035
30792
  }
@@ -30166,6 +30923,20 @@ class EmbeddedClient {
30166
30923
  getSessionId() {
30167
30924
  return this.session.getSessionId();
30168
30925
  }
30926
+ async getCommands() {
30927
+ if (!this.initialized) {
30928
+ await this.initialize();
30929
+ }
30930
+ return this.agent.getCommands();
30931
+ }
30932
+ getTokenUsage() {
30933
+ return this.agent.getTokenUsage();
30934
+ }
30935
+ clearConversation() {
30936
+ this.agent.clearConversation();
30937
+ this.messages = [];
30938
+ this.logger.info("Conversation cleared");
30939
+ }
30169
30940
  }
30170
30941
  // packages/terminal/src/components/Input.tsx
30171
30942
  var import_react23 = __toESM(require_react(), 1);
@@ -30348,7 +31119,7 @@ function parseMarkdown(text) {
30348
31119
  // packages/terminal/src/components/Messages.tsx
30349
31120
  var jsx_dev_runtime3 = __toESM(require_jsx_dev_runtime(), 1);
30350
31121
  function Messages4({ messages, currentResponse, currentToolCall, lastToolResult, activityLog = [] }) {
30351
- const visibleMessages = messages.slice(-15);
31122
+ const visibleMessages = messages.slice(-10);
30352
31123
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
30353
31124
  flexDirection: "column",
30354
31125
  children: [
@@ -30356,59 +31127,49 @@ function Messages4({ messages, currentResponse, currentToolCall, lastToolResult,
30356
31127
  message
30357
31128
  }, message.id, false, undefined, this)),
30358
31129
  activityLog.map((entry) => {
30359
- if (entry.type === "tool_call" && entry.toolCall) {
31130
+ if (entry.type === "text" && entry.content) {
30360
31131
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
30361
31132
  marginY: 1,
30362
31133
  children: [
30363
31134
  /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
30364
31135
  dimColor: true,
30365
- children: "\u25D0 "
31136
+ children: "\u25CF "
30366
31137
  }, undefined, false, undefined, this),
30367
- /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
30368
- dimColor: true,
30369
- children: formatToolCall(entry.toolCall)
31138
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31139
+ flexGrow: 1,
31140
+ children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Markdown, {
31141
+ content: entry.content
31142
+ }, undefined, false, undefined, this)
30370
31143
  }, undefined, false, undefined, this)
30371
31144
  ]
30372
31145
  }, entry.id, true, undefined, this);
30373
31146
  }
31147
+ if (entry.type === "tool_call" && entry.toolCall) {
31148
+ return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31149
+ marginTop: 1,
31150
+ children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
31151
+ dimColor: true,
31152
+ children: [
31153
+ " \u25D0 ",
31154
+ formatToolCall(entry.toolCall)
31155
+ ]
31156
+ }, undefined, true, undefined, this)
31157
+ }, entry.id, false, undefined, this);
31158
+ }
30374
31159
  if (entry.type === "tool_result" && entry.toolResult) {
30375
31160
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
30376
- marginLeft: 2,
31161
+ marginBottom: 1,
30377
31162
  children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
30378
31163
  dimColor: true,
30379
31164
  children: [
30380
- "\u2192 ",
30381
- truncate(entry.toolResult.content, 100)
31165
+ " \u2192 ",
31166
+ truncate(entry.toolResult.content, 80)
30382
31167
  ]
30383
31168
  }, undefined, true, undefined, this)
30384
31169
  }, entry.id, false, undefined, this);
30385
31170
  }
30386
31171
  return null;
30387
31172
  }),
30388
- currentToolCall && !activityLog.some((e) => e.toolCall?.id === currentToolCall.id) && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
30389
- marginY: 1,
30390
- children: [
30391
- /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
30392
- dimColor: true,
30393
- children: "\u25D0 "
30394
- }, undefined, false, undefined, this),
30395
- /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
30396
- dimColor: true,
30397
- children: formatToolCall(currentToolCall)
30398
- }, undefined, false, undefined, this)
30399
- ]
30400
- }, undefined, true, undefined, this),
30401
- lastToolResult && !activityLog.some((e) => e.toolResult?.toolCallId === lastToolResult.toolCallId) && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
30402
- marginY: 1,
30403
- marginLeft: 2,
30404
- children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
30405
- dimColor: true,
30406
- children: [
30407
- "\u2192 ",
30408
- truncate(lastToolResult.content, 100)
30409
- ]
30410
- }, undefined, true, undefined, this)
30411
- }, undefined, false, undefined, this),
30412
31173
  currentResponse && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
30413
31174
  marginY: 1,
30414
31175
  children: [
@@ -30423,7 +31184,17 @@ function Messages4({ messages, currentResponse, currentToolCall, lastToolResult,
30423
31184
  }, undefined, false, undefined, this)
30424
31185
  }, undefined, false, undefined, this)
30425
31186
  ]
30426
- }, undefined, true, undefined, this)
31187
+ }, undefined, true, undefined, this),
31188
+ currentToolCall && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
31189
+ marginTop: 1,
31190
+ children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
31191
+ dimColor: true,
31192
+ children: [
31193
+ " \u25D0 ",
31194
+ formatToolCall(currentToolCall)
31195
+ ]
31196
+ }, undefined, true, undefined, this)
31197
+ }, undefined, false, undefined, this)
30427
31198
  ]
30428
31199
  }, undefined, true, undefined, this);
30429
31200
  }
@@ -30484,6 +31255,12 @@ function formatToolCall(toolCall) {
30484
31255
  switch (name) {
30485
31256
  case "bash":
30486
31257
  return `Running: ${truncate(String(input.command || ""), 60)}`;
31258
+ case "curl":
31259
+ return `Fetching: ${truncate(String(input.url || ""), 60)}`;
31260
+ case "web_fetch":
31261
+ return `Fetching: ${truncate(String(input.url || ""), 60)}`;
31262
+ case "web_search":
31263
+ return `Searching: ${truncate(String(input.query || ""), 60)}`;
30487
31264
  case "read":
30488
31265
  return `Reading: ${truncate(String(input.path || input.file_path || ""), 60)}`;
30489
31266
  case "write":
@@ -30516,10 +31293,17 @@ function truncate(text, maxLength) {
30516
31293
 
30517
31294
  // packages/terminal/src/components/Status.tsx
30518
31295
  var jsx_dev_runtime4 = __toESM(require_jsx_dev_runtime(), 1);
30519
- function Status({ isProcessing, cwd: cwd2, queueLength = 0 }) {
30520
- const maxCwdLength = 50;
31296
+ function Status({ isProcessing, cwd: cwd2, queueLength = 0, tokenUsage }) {
31297
+ const maxCwdLength = 30;
30521
31298
  const displayCwd = cwd2.length > maxCwdLength ? "..." + cwd2.slice(-(maxCwdLength - 3)) : cwd2;
30522
31299
  const queueInfo = queueLength > 0 ? ` | ${queueLength} queued` : "";
31300
+ let tokenInfo = "";
31301
+ if (tokenUsage && tokenUsage.totalTokens > 0) {
31302
+ const used = Math.round(tokenUsage.totalTokens / 1000);
31303
+ const max = Math.round(tokenUsage.maxContextTokens / 1000);
31304
+ const percent = Math.round(tokenUsage.totalTokens / tokenUsage.maxContextTokens * 100);
31305
+ tokenInfo = ` | ${used}k/${max}k (${percent}%)`;
31306
+ }
30523
31307
  return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
30524
31308
  marginTop: 1,
30525
31309
  borderStyle: "single",
@@ -30537,6 +31321,10 @@ function Status({ isProcessing, cwd: cwd2, queueLength = 0 }) {
30537
31321
  dimColor: !isProcessing,
30538
31322
  children: isProcessing ? "\u25CF processing" : "\u25CF ready"
30539
31323
  }, undefined, false, undefined, this),
31324
+ /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
31325
+ dimColor: true,
31326
+ children: tokenInfo
31327
+ }, undefined, false, undefined, this),
30540
31328
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
30541
31329
  dimColor: true,
30542
31330
  children: queueInfo
@@ -30545,7 +31333,8 @@ function Status({ isProcessing, cwd: cwd2, queueLength = 0 }) {
30545
31333
  dimColor: true,
30546
31334
  children: [
30547
31335
  " | ",
30548
- isProcessing ? "Esc to stop" : "Ctrl+C to exit"
31336
+ isProcessing ? "Esc to stop" : "Ctrl+C to exit",
31337
+ " | /help"
30549
31338
  ]
30550
31339
  }, undefined, true, undefined, this)
30551
31340
  ]
@@ -30611,6 +31400,7 @@ function App2({ cwd: cwd2 }) {
30611
31400
  const [error, setError] = import_react25.useState(null);
30612
31401
  const [messageQueue, setMessageQueue] = import_react25.useState([]);
30613
31402
  const [activityLog, setActivityLog] = import_react25.useState([]);
31403
+ const [tokenUsage, setTokenUsage] = import_react25.useState();
30614
31404
  const responseRef = import_react25.useRef("");
30615
31405
  const clientRef = import_react25.useRef(null);
30616
31406
  const toolCallsRef = import_react25.useRef([]);
@@ -30648,6 +31438,19 @@ function App2({ cwd: cwd2 }) {
30648
31438
  responseRef.current += chunk.content;
30649
31439
  setCurrentResponse(responseRef.current);
30650
31440
  } else if (chunk.type === "tool_use" && chunk.toolCall) {
31441
+ if (responseRef.current.trim()) {
31442
+ setActivityLog((prev) => [
31443
+ ...prev,
31444
+ {
31445
+ id: generateId(),
31446
+ type: "text",
31447
+ content: responseRef.current,
31448
+ timestamp: now()
31449
+ }
31450
+ ]);
31451
+ setCurrentResponse("");
31452
+ responseRef.current = "";
31453
+ }
30651
31454
  toolCallsRef.current.push(chunk.toolCall);
30652
31455
  setActivityLog((prev) => [
30653
31456
  ...prev,
@@ -30659,7 +31462,6 @@ function App2({ cwd: cwd2 }) {
30659
31462
  }
30660
31463
  ]);
30661
31464
  setCurrentToolCall(chunk.toolCall);
30662
- setLastToolResult(undefined);
30663
31465
  } else if (chunk.type === "tool_result" && chunk.toolResult) {
30664
31466
  toolResultsRef.current.push(chunk.toolResult);
30665
31467
  setActivityLog((prev) => [
@@ -30671,32 +31473,50 @@ function App2({ cwd: cwd2 }) {
30671
31473
  timestamp: now()
30672
31474
  }
30673
31475
  ]);
30674
- setLastToolResult(chunk.toolResult);
30675
31476
  setCurrentToolCall(undefined);
30676
31477
  } else if (chunk.type === "error" && chunk.error) {
30677
31478
  setError(chunk.error);
30678
31479
  setIsProcessing(false);
31480
+ } else if (chunk.type === "usage" && chunk.usage) {
31481
+ setTokenUsage(chunk.usage);
30679
31482
  } else if (chunk.type === "done") {
30680
- if (responseRef.current || toolCallsRef.current.length > 0) {
31483
+ if (responseRef.current.trim()) {
31484
+ setActivityLog((prev) => [
31485
+ ...prev,
31486
+ {
31487
+ id: generateId(),
31488
+ type: "text",
31489
+ content: responseRef.current,
31490
+ timestamp: now()
31491
+ }
31492
+ ]);
31493
+ }
31494
+ const fullContent = activityLog.filter((e) => e.type === "text").map((e) => e.content).join(`
31495
+ `) + (responseRef.current ? `
31496
+ ` + responseRef.current : "");
31497
+ if (fullContent.trim() || toolCallsRef.current.length > 0) {
30681
31498
  setMessages((prev) => [
30682
31499
  ...prev,
30683
31500
  {
30684
31501
  id: generateId(),
30685
31502
  role: "assistant",
30686
- content: responseRef.current,
31503
+ content: fullContent.trim(),
30687
31504
  timestamp: now(),
30688
31505
  toolCalls: toolCallsRef.current.length > 0 ? [...toolCallsRef.current] : undefined,
30689
31506
  toolResults: toolResultsRef.current.length > 0 ? [...toolResultsRef.current] : undefined
30690
31507
  }
30691
31508
  ]);
30692
- setCurrentResponse("");
30693
- responseRef.current = "";
30694
- toolCallsRef.current = [];
30695
- toolResultsRef.current = [];
30696
31509
  }
31510
+ setCurrentResponse("");
31511
+ responseRef.current = "";
31512
+ toolCallsRef.current = [];
31513
+ toolResultsRef.current = [];
30697
31514
  setCurrentToolCall(undefined);
30698
- setLastToolResult(undefined);
31515
+ setActivityLog([]);
30699
31516
  setIsProcessing(false);
31517
+ if (newClient) {
31518
+ setTokenUsage(newClient.getTokenUsage());
31519
+ }
30700
31520
  }
30701
31521
  });
30702
31522
  newClient.onError((err) => {
@@ -30864,7 +31684,8 @@ function App2({ cwd: cwd2 }) {
30864
31684
  /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Status, {
30865
31685
  isProcessing,
30866
31686
  cwd: cwd2,
30867
- queueLength: messageQueue.length
31687
+ queueLength: messageQueue.length,
31688
+ tokenUsage
30868
31689
  }, undefined, false, undefined, this)
30869
31690
  ]
30870
31691
  }, undefined, true, undefined, this);
@@ -30879,7 +31700,7 @@ var options = {
30879
31700
  help: args.includes("--help") || args.includes("-h")
30880
31701
  };
30881
31702
  if (options.version) {
30882
- console.log("oldpal v0.1.8");
31703
+ console.log("oldpal v0.2.0");
30883
31704
  process.exit(0);
30884
31705
  }
30885
31706
  if (options.help) {
@@ -30910,4 +31731,4 @@ waitUntilExit().then(() => {
30910
31731
  process.exit(0);
30911
31732
  });
30912
31733
 
30913
- //# debugId=43BB59ABD8BF8C1564756E2164756E21
31734
+ //# debugId=21C263FACC119E6E64756E2164756E21