@hasna/oldpal 0.1.8 → 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]+)["']?/);
@@ -29643,7 +29643,609 @@ class HookExecutor {
29643
29643
  return null;
29644
29644
  }
29645
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}
29646
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
30244
+
30245
+ If there are staged changes, review those. Otherwise, ask what to review.`
30246
+ };
30247
+ }
30248
+ }
29647
30249
  // packages/core/src/llm/client.ts
29648
30250
  async function createLLMClient(config) {
29649
30251
  if (config.provider === "anthropic") {
@@ -29654,8 +30256,8 @@ async function createLLMClient(config) {
29654
30256
  }
29655
30257
 
29656
30258
  // packages/core/src/config.ts
29657
- import { join as join5 } from "path";
29658
- import { homedir as homedir4 } from "os";
30259
+ import { join as join7 } from "path";
30260
+ import { homedir as homedir6 } from "os";
29659
30261
  var DEFAULT_CONFIG = {
29660
30262
  llm: {
29661
30263
  provider: "anthropic",
@@ -29685,13 +30287,13 @@ var DEFAULT_CONFIG = {
29685
30287
  ]
29686
30288
  };
29687
30289
  function getConfigDir() {
29688
- return join5(homedir4(), ".oldpal");
30290
+ return join7(homedir6(), ".oldpal");
29689
30291
  }
29690
30292
  function getConfigPath(filename) {
29691
- return join5(getConfigDir(), filename);
30293
+ return join7(getConfigDir(), filename);
29692
30294
  }
29693
30295
  function getProjectConfigDir(cwd2 = process.cwd()) {
29694
- return join5(cwd2, ".oldpal");
30296
+ return join7(cwd2, ".oldpal");
29695
30297
  }
29696
30298
  async function loadConfig(cwd2 = process.cwd()) {
29697
30299
  const config = { ...DEFAULT_CONFIG };
@@ -29704,7 +30306,7 @@ async function loadConfig(cwd2 = process.cwd()) {
29704
30306
  if (userConfig.voice)
29705
30307
  config.voice = { ...config.voice, ...userConfig.voice };
29706
30308
  }
29707
- const projectConfigPath = join5(getProjectConfigDir(cwd2), "settings.json");
30309
+ const projectConfigPath = join7(getProjectConfigDir(cwd2), "settings.json");
29708
30310
  const projectConfig = await loadJsonFile(projectConfigPath);
29709
30311
  if (projectConfig) {
29710
30312
  Object.assign(config, projectConfig);
@@ -29713,7 +30315,7 @@ async function loadConfig(cwd2 = process.cwd()) {
29713
30315
  if (projectConfig.voice)
29714
30316
  config.voice = { ...config.voice, ...projectConfig.voice };
29715
30317
  }
29716
- const localConfigPath = join5(getProjectConfigDir(cwd2), "settings.local.json");
30318
+ const localConfigPath = join7(getProjectConfigDir(cwd2), "settings.local.json");
29717
30319
  const localConfig = await loadJsonFile(localConfigPath);
29718
30320
  if (localConfig) {
29719
30321
  Object.assign(config, localConfig);
@@ -29731,7 +30333,7 @@ async function loadHooksConfig(cwd2 = process.cwd()) {
29731
30333
  if (userHooks?.hooks) {
29732
30334
  mergeHooks(hooks, userHooks.hooks);
29733
30335
  }
29734
- const projectHooksPath = join5(getProjectConfigDir(cwd2), "hooks.json");
30336
+ const projectHooksPath = join7(getProjectConfigDir(cwd2), "hooks.json");
29735
30337
  const projectHooks = await loadJsonFile(projectHooksPath);
29736
30338
  if (projectHooks?.hooks) {
29737
30339
  mergeHooks(hooks, projectHooks.hooks);
@@ -29767,16 +30369,22 @@ class AgentLoop {
29767
30369
  skillExecutor;
29768
30370
  hookLoader;
29769
30371
  hookExecutor;
30372
+ commandLoader;
30373
+ commandExecutor;
30374
+ builtinCommands;
29770
30375
  llmClient = null;
29771
30376
  config = null;
29772
30377
  cwd;
30378
+ sessionId;
29773
30379
  isRunning = false;
29774
30380
  shouldStop = false;
29775
30381
  onChunk;
29776
30382
  onToolStart;
29777
30383
  onToolEnd;
30384
+ onTokenUsage;
29778
30385
  constructor(options = {}) {
29779
30386
  this.cwd = options.cwd || process.cwd();
30387
+ this.sessionId = options.sessionId || generateId();
29780
30388
  this.context = new AgentContext;
29781
30389
  this.toolRegistry = new ToolRegistry;
29782
30390
  this.connectorBridge = new ConnectorBridge;
@@ -29784,9 +30392,13 @@ class AgentLoop {
29784
30392
  this.skillExecutor = new SkillExecutor;
29785
30393
  this.hookLoader = new HookLoader;
29786
30394
  this.hookExecutor = new HookExecutor;
30395
+ this.commandLoader = new CommandLoader(this.cwd);
30396
+ this.commandExecutor = new CommandExecutor(this.commandLoader);
30397
+ this.builtinCommands = new BuiltinCommands;
29787
30398
  this.onChunk = options.onChunk;
29788
30399
  this.onToolStart = options.onToolStart;
29789
30400
  this.onToolEnd = options.onToolEnd;
30401
+ this.onTokenUsage = options.onTokenUsage;
29790
30402
  }
29791
30403
  async initialize() {
29792
30404
  this.config = await loadConfig(this.cwd);
@@ -29797,10 +30409,12 @@ class AgentLoop {
29797
30409
  await this.connectorBridge.discover(this.config.connectors);
29798
30410
  this.connectorBridge.registerAll(this.toolRegistry);
29799
30411
  await this.skillLoader.loadAll(this.cwd);
30412
+ await this.commandLoader.loadAll();
30413
+ this.builtinCommands.registerAll(this.commandLoader);
29800
30414
  const hooksConfig = await loadHooksConfig(this.cwd);
29801
30415
  this.hookLoader.load(hooksConfig);
29802
30416
  await this.hookExecutor.execute(this.hookLoader.getHooks("SessionStart"), {
29803
- session_id: generateId(),
30417
+ session_id: this.sessionId,
29804
30418
  hook_event_name: "SessionStart",
29805
30419
  cwd: this.cwd
29806
30420
  });
@@ -29816,7 +30430,7 @@ class AgentLoop {
29816
30430
  this.shouldStop = false;
29817
30431
  try {
29818
30432
  const promptHookResult = await this.hookExecutor.execute(this.hookLoader.getHooks("UserPromptSubmit"), {
29819
- session_id: generateId(),
30433
+ session_id: this.sessionId,
29820
30434
  hook_event_name: "UserPromptSubmit",
29821
30435
  cwd: this.cwd,
29822
30436
  prompt: userMessage
@@ -29825,6 +30439,18 @@ class AgentLoop {
29825
30439
  this.emit({ type: "error", error: promptHookResult.stopReason || "Blocked by hook" });
29826
30440
  return;
29827
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
+ }
29828
30454
  if (userMessage.startsWith("/")) {
29829
30455
  const handled = await this.handleSkillInvocation(userMessage);
29830
30456
  if (handled)
@@ -29905,6 +30531,30 @@ class AgentLoop {
29905
30531
  }
29906
30532
  return results;
29907
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
+ }
29908
30558
  async handleSkillInvocation(message) {
29909
30559
  const match = message.match(/^\/(\S+)(?:\s+(.*))?$/);
29910
30560
  if (!match)
@@ -29936,18 +30586,34 @@ class AgentLoop {
29936
30586
  getSkills() {
29937
30587
  return this.skillLoader.getSkills();
29938
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
+ }
29939
30599
  isProcessing() {
29940
30600
  return this.isRunning;
29941
30601
  }
30602
+ getSessionId() {
30603
+ return this.sessionId;
30604
+ }
30605
+ clearConversation() {
30606
+ this.context = new AgentContext;
30607
+ }
29942
30608
  }
29943
30609
 
29944
30610
  // packages/core/src/index.ts
29945
30611
  init_anthropic();
29946
30612
 
29947
30613
  // 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";
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";
29951
30617
 
29952
30618
  class Logger {
29953
30619
  logDir;
@@ -29955,14 +30621,14 @@ class Logger {
29955
30621
  sessionId;
29956
30622
  constructor(sessionId) {
29957
30623
  this.sessionId = sessionId;
29958
- this.logDir = join6(homedir5(), ".oldpal", "logs");
30624
+ this.logDir = join8(homedir7(), ".oldpal", "logs");
29959
30625
  this.ensureDir(this.logDir);
29960
30626
  const date = new Date().toISOString().split("T")[0];
29961
- this.logFile = join6(this.logDir, `${date}.log`);
30627
+ this.logFile = join8(this.logDir, `${date}.log`);
29962
30628
  }
29963
30629
  ensureDir(dir) {
29964
- if (!existsSync3(dir)) {
29965
- mkdirSync(dir, { recursive: true });
30630
+ if (!existsSync5(dir)) {
30631
+ mkdirSync2(dir, { recursive: true });
29966
30632
  }
29967
30633
  }
29968
30634
  write(level, message, data) {
@@ -30001,13 +30667,13 @@ class SessionStorage {
30001
30667
  sessionId;
30002
30668
  constructor(sessionId) {
30003
30669
  this.sessionId = sessionId;
30004
- this.sessionsDir = join6(homedir5(), ".oldpal", "sessions");
30670
+ this.sessionsDir = join8(homedir7(), ".oldpal", "sessions");
30005
30671
  this.ensureDir(this.sessionsDir);
30006
- this.sessionFile = join6(this.sessionsDir, `${sessionId}.json`);
30672
+ this.sessionFile = join8(this.sessionsDir, `${sessionId}.json`);
30007
30673
  }
30008
30674
  ensureDir(dir) {
30009
- if (!existsSync3(dir)) {
30010
- mkdirSync(dir, { recursive: true });
30675
+ if (!existsSync5(dir)) {
30676
+ mkdirSync2(dir, { recursive: true });
30011
30677
  }
30012
30678
  }
30013
30679
  save(data) {
@@ -30020,16 +30686,16 @@ class SessionStorage {
30020
30686
  }
30021
30687
  }
30022
30688
  function initOldpalDir() {
30023
- const baseDir = join6(homedir5(), ".oldpal");
30689
+ const baseDir = join8(homedir7(), ".oldpal");
30024
30690
  const dirs = [
30025
30691
  baseDir,
30026
- join6(baseDir, "sessions"),
30027
- join6(baseDir, "logs"),
30028
- join6(baseDir, "skills")
30692
+ join8(baseDir, "sessions"),
30693
+ join8(baseDir, "logs"),
30694
+ join8(baseDir, "skills")
30029
30695
  ];
30030
30696
  for (const dir of dirs) {
30031
- if (!existsSync3(dir)) {
30032
- mkdirSync(dir, { recursive: true });
30697
+ if (!existsSync5(dir)) {
30698
+ mkdirSync2(dir, { recursive: true });
30033
30699
  }
30034
30700
  }
30035
30701
  }
@@ -30166,6 +30832,20 @@ class EmbeddedClient {
30166
30832
  getSessionId() {
30167
30833
  return this.session.getSessionId();
30168
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
+ }
30169
30849
  }
30170
30850
  // packages/terminal/src/components/Input.tsx
30171
30851
  var import_react23 = __toESM(require_react(), 1);
@@ -30516,10 +31196,17 @@ function truncate(text, maxLength) {
30516
31196
 
30517
31197
  // packages/terminal/src/components/Status.tsx
30518
31198
  var jsx_dev_runtime4 = __toESM(require_jsx_dev_runtime(), 1);
30519
- function Status({ isProcessing, cwd: cwd2, queueLength = 0 }) {
30520
- const maxCwdLength = 50;
31199
+ function Status({ isProcessing, cwd: cwd2, queueLength = 0, tokenUsage }) {
31200
+ const maxCwdLength = 30;
30521
31201
  const displayCwd = cwd2.length > maxCwdLength ? "..." + cwd2.slice(-(maxCwdLength - 3)) : cwd2;
30522
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
+ }
30523
31210
  return /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Box_default, {
30524
31211
  marginTop: 1,
30525
31212
  borderStyle: "single",
@@ -30537,6 +31224,10 @@ function Status({ isProcessing, cwd: cwd2, queueLength = 0 }) {
30537
31224
  dimColor: !isProcessing,
30538
31225
  children: isProcessing ? "\u25CF processing" : "\u25CF ready"
30539
31226
  }, undefined, false, undefined, this),
31227
+ /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
31228
+ dimColor: true,
31229
+ children: tokenInfo
31230
+ }, undefined, false, undefined, this),
30540
31231
  /* @__PURE__ */ jsx_dev_runtime4.jsxDEV(Text, {
30541
31232
  dimColor: true,
30542
31233
  children: queueInfo
@@ -30545,7 +31236,8 @@ function Status({ isProcessing, cwd: cwd2, queueLength = 0 }) {
30545
31236
  dimColor: true,
30546
31237
  children: [
30547
31238
  " | ",
30548
- isProcessing ? "Esc to stop" : "Ctrl+C to exit"
31239
+ isProcessing ? "Esc to stop" : "Ctrl+C to exit",
31240
+ " | /help"
30549
31241
  ]
30550
31242
  }, undefined, true, undefined, this)
30551
31243
  ]
@@ -30611,6 +31303,7 @@ function App2({ cwd: cwd2 }) {
30611
31303
  const [error, setError] = import_react25.useState(null);
30612
31304
  const [messageQueue, setMessageQueue] = import_react25.useState([]);
30613
31305
  const [activityLog, setActivityLog] = import_react25.useState([]);
31306
+ const [tokenUsage, setTokenUsage] = import_react25.useState();
30614
31307
  const responseRef = import_react25.useRef("");
30615
31308
  const clientRef = import_react25.useRef(null);
30616
31309
  const toolCallsRef = import_react25.useRef([]);
@@ -30676,6 +31369,8 @@ function App2({ cwd: cwd2 }) {
30676
31369
  } else if (chunk.type === "error" && chunk.error) {
30677
31370
  setError(chunk.error);
30678
31371
  setIsProcessing(false);
31372
+ } else if (chunk.type === "usage" && chunk.usage) {
31373
+ setTokenUsage(chunk.usage);
30679
31374
  } else if (chunk.type === "done") {
30680
31375
  if (responseRef.current || toolCallsRef.current.length > 0) {
30681
31376
  setMessages((prev) => [
@@ -30697,6 +31392,9 @@ function App2({ cwd: cwd2 }) {
30697
31392
  setCurrentToolCall(undefined);
30698
31393
  setLastToolResult(undefined);
30699
31394
  setIsProcessing(false);
31395
+ if (newClient) {
31396
+ setTokenUsage(newClient.getTokenUsage());
31397
+ }
30700
31398
  }
30701
31399
  });
30702
31400
  newClient.onError((err) => {
@@ -30864,7 +31562,8 @@ function App2({ cwd: cwd2 }) {
30864
31562
  /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Status, {
30865
31563
  isProcessing,
30866
31564
  cwd: cwd2,
30867
- queueLength: messageQueue.length
31565
+ queueLength: messageQueue.length,
31566
+ tokenUsage
30868
31567
  }, undefined, false, undefined, this)
30869
31568
  ]
30870
31569
  }, undefined, true, undefined, this);
@@ -30879,7 +31578,7 @@ var options = {
30879
31578
  help: args.includes("--help") || args.includes("-h")
30880
31579
  };
30881
31580
  if (options.version) {
30882
- console.log("oldpal v0.1.8");
31581
+ console.log("oldpal v0.1.9");
30883
31582
  process.exit(0);
30884
31583
  }
30885
31584
  if (options.help) {
@@ -30910,4 +31609,4 @@ waitUntilExit().then(() => {
30910
31609
  process.exit(0);
30911
31610
  });
30912
31611
 
30913
- //# debugId=43BB59ABD8BF8C1564756E2164756E21
31612
+ //# debugId=307434E7D5151E5864756E2164756E21