lovecode-ai 0.1.6 → 0.1.7

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.
Files changed (2) hide show
  1. package/dist/index.js +311 -15
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6888,19 +6888,293 @@ function startTUI(props) {
6888
6888
  }
6889
6889
 
6890
6890
  // src/commands/tui.ts
6891
+ var TOOL_SYSTEM_PROMPT = `You are LoveCode AI, a terminal-native coding assistant with full access to the project filesystem.
6892
+
6893
+ You can read, write, edit, and search files using tool tags:
6894
+
6895
+ <tool name="read_file">
6896
+ path=<file path>
6897
+ </tool>
6898
+
6899
+ <tool name="write_file">
6900
+ path=<file path>
6901
+ content=<file content>
6902
+ </tool>
6903
+
6904
+ <tool name="edit_file">
6905
+ path=<file path>
6906
+ oldString=<text to replace>
6907
+ newString=<replacement text>
6908
+ </tool>
6909
+
6910
+ <tool name="create_file">
6911
+ path=<file path>
6912
+ </tool>
6913
+
6914
+ <tool name="delete_file">
6915
+ path=<file path>
6916
+ </tool>
6917
+
6918
+ <tool name="append_file">
6919
+ path=<file path>
6920
+ content=<text to append>
6921
+ </tool>
6922
+
6923
+ <tool name="grep_search">
6924
+ pattern=<regex pattern>
6925
+ </tool>
6926
+
6927
+ <tool name="glob_search">
6928
+ pattern=<glob pattern>
6929
+ </tool>
6930
+
6931
+ <tool name="read_dir">
6932
+ path=<directory path>
6933
+ </tool>
6934
+
6935
+ <tool name="execute_command">
6936
+ command=<shell command>
6937
+ </tool>
6938
+
6939
+ When you need to access files, use the appropriate tool tag.
6940
+ After getting results, continue helping the user.`;
6941
+ async function loadAIProvider() {
6942
+ try {
6943
+ const { loadEnv: loadEnv2 } = await import("./env-HJQWWL6N.js");
6944
+ const { loadConfig: loadConfig2 } = await import("./config-FJNTTKR3.js");
6945
+ const { resolveModel } = await import("./registry-ADSIKXA4.js");
6946
+ loadEnv2();
6947
+ const cfg = loadConfig2();
6948
+ if (!cfg.provider) {
6949
+ console.log(chalk35.yellow("No AI provider configured. Use `lovecode init` to set one up."));
6950
+ return null;
6951
+ }
6952
+ const resolved = resolveModel(cfg.provider);
6953
+ if (!resolved) {
6954
+ console.log(chalk35.yellow(`Provider "${cfg.provider}" not found. Use \`lovecode init\` to reconfigure.`));
6955
+ return null;
6956
+ }
6957
+ const provider = resolved.entry.provider;
6958
+ const model = resolved.model;
6959
+ const config = {
6960
+ model,
6961
+ temperature: cfg.model_params?.temperature ?? 0.2,
6962
+ maxTokens: cfg.model_params?.max_tokens ?? 4096,
6963
+ baseUrl: cfg.api?.base_url
6964
+ };
6965
+ return { provider, config };
6966
+ } catch (err) {
6967
+ console.log(chalk35.red(`Failed to load AI provider: ${err.message}`));
6968
+ return null;
6969
+ }
6970
+ }
6971
+ async function processToolCalls(response, workingDir) {
6972
+ const outputs = [];
6973
+ const toolRegex = /<tool\s+name="([^"]+)">\n?([\s\S]*?)<\/tool>/g;
6974
+ let match;
6975
+ while ((match = toolRegex.exec(response)) !== null) {
6976
+ const toolName = match[1];
6977
+ const argsBlock = match[2].trim();
6978
+ const args = {};
6979
+ for (const line of argsBlock.split("\n")) {
6980
+ const eqIdx = line.indexOf("=");
6981
+ if (eqIdx > 0) {
6982
+ const key = line.slice(0, eqIdx).trim();
6983
+ const value = line.slice(eqIdx + 1).trim();
6984
+ args[key] = value;
6985
+ }
6986
+ }
6987
+ try {
6988
+ const tool = await getTool2(toolName);
6989
+ if (!tool) {
6990
+ outputs.push(`Unknown tool: ${toolName}`);
6991
+ continue;
6992
+ }
6993
+ const result = await tool.execute(workingDir, args);
6994
+ const output = result.success ? `[${toolName}] result:
6995
+ ${result.output.slice(0, 3e3)}` : `[${toolName}] error: ${result.error || result.output}`;
6996
+ outputs.push(output);
6997
+ } catch (err) {
6998
+ outputs.push(`[${toolName}] exception: ${err.message}`);
6999
+ }
7000
+ }
7001
+ return outputs;
7002
+ }
7003
+ async function getTool2(name) {
7004
+ if (name === "read_file") {
7005
+ const { readFileSync: readFileSync18, existsSync: existsSync16 } = await import("fs");
7006
+ const { resolve: resolve5 } = await import("path");
7007
+ return {
7008
+ execute: async (workingDir, args) => {
7009
+ const filePath = resolve5(workingDir, args.path || ".");
7010
+ if (!existsSync16(filePath)) return { success: false, output: "", error: `File not found: ${args.path}` };
7011
+ const content = readFileSync18(filePath, "utf-8");
7012
+ const lines = content.split("\n");
7013
+ const numbered = lines.map((l, i) => `${String(i + 1).padStart(4, " ")} | ${l}`).join("\n");
7014
+ return { success: true, output: numbered };
7015
+ }
7016
+ };
7017
+ }
7018
+ if (name === "write_file") {
7019
+ const { writeFileSync: writeFileSync11, mkdirSync: mkdirSync10 } = await import("fs");
7020
+ const { resolve: resolve5, dirname: dirname6 } = await import("path");
7021
+ return {
7022
+ execute: async (workingDir, args) => {
7023
+ const filePath = resolve5(workingDir, args.path || "");
7024
+ mkdirSync10(dirname6(filePath), { recursive: true });
7025
+ writeFileSync11(filePath, args.content || "", "utf-8");
7026
+ return { success: true, output: `Wrote ${filePath}` };
7027
+ }
7028
+ };
7029
+ }
7030
+ if (name === "edit_file") {
7031
+ const { readFileSync: readFileSync18, writeFileSync: writeFileSync11, existsSync: existsSync16 } = await import("fs");
7032
+ const { resolve: resolve5 } = await import("path");
7033
+ return {
7034
+ execute: async (workingDir, args) => {
7035
+ const filePath = resolve5(workingDir, args.path || "");
7036
+ if (!existsSync16(filePath)) return { success: false, output: "", error: `File not found: ${args.path}` };
7037
+ const content = readFileSync18(filePath, "utf-8");
7038
+ if (!args.oldString) return { success: false, output: "", error: "oldString required" };
7039
+ const escaped = args.oldString.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7040
+ const count = (content.match(new RegExp(escaped, "g")) || []).length;
7041
+ if (count === 0) return { success: false, output: "", error: `oldString not found in ${args.path}` };
7042
+ const updated = content.replaceAll(args.oldString, args.newString || "");
7043
+ writeFileSync11(filePath, updated, "utf-8");
7044
+ return { success: true, output: `Edited ${filePath} (${count} replacement${count > 1 ? "s" : ""})` };
7045
+ }
7046
+ };
7047
+ }
7048
+ if (name === "create_file") {
7049
+ const { writeFileSync: writeFileSync11, mkdirSync: mkdirSync10, existsSync: existsSync16 } = await import("fs");
7050
+ const { resolve: resolve5, dirname: dirname6 } = await import("path");
7051
+ return {
7052
+ execute: async (workingDir, args) => {
7053
+ const filePath = resolve5(workingDir, args.path || "");
7054
+ if (existsSync16(filePath)) return { success: false, output: "", error: `File exists: ${args.path}` };
7055
+ mkdirSync10(dirname6(filePath), { recursive: true });
7056
+ writeFileSync11(filePath, "", "utf-8");
7057
+ return { success: true, output: `Created ${filePath}` };
7058
+ }
7059
+ };
7060
+ }
7061
+ if (name === "delete_file") {
7062
+ const { unlinkSync: unlinkSync7, existsSync: existsSync16 } = await import("fs");
7063
+ const { resolve: resolve5 } = await import("path");
7064
+ return {
7065
+ execute: async (workingDir, args) => {
7066
+ const filePath = resolve5(workingDir, args.path || "");
7067
+ if (!existsSync16(filePath)) return { success: false, output: "", error: `File not found: ${args.path}` };
7068
+ unlinkSync7(filePath);
7069
+ return { success: true, output: `Deleted ${filePath}` };
7070
+ }
7071
+ };
7072
+ }
7073
+ if (name === "append_file") {
7074
+ const { appendFileSync: appendFileSync2, mkdirSync: mkdirSync10 } = await import("fs");
7075
+ const { resolve: resolve5, dirname: dirname6 } = await import("path");
7076
+ return {
7077
+ execute: async (workingDir, args) => {
7078
+ const filePath = resolve5(workingDir, args.path || "");
7079
+ mkdirSync10(dirname6(filePath), { recursive: true });
7080
+ appendFileSync2(filePath, (args.content || "") + "\n", "utf-8");
7081
+ return { success: true, output: `Appended to ${filePath}` };
7082
+ }
7083
+ };
7084
+ }
7085
+ if (name === "grep_search") {
7086
+ const { execSync: execSync5 } = await import("child_process");
7087
+ return {
7088
+ execute: async (workingDir, args) => {
7089
+ try {
7090
+ const pattern = args.pattern || "";
7091
+ const cmd = `rg -n '${pattern.replace(/'/g, "'\\''")}' 2>/dev/null || echo "(no matches)"`;
7092
+ const output = execSync5(cmd, { cwd: workingDir, encoding: "utf-8", maxBuffer: 1024 * 1024 });
7093
+ return { success: true, output: output.trim() };
7094
+ } catch {
7095
+ return { success: true, output: "(no matches)" };
7096
+ }
7097
+ }
7098
+ };
7099
+ }
7100
+ if (name === "glob_search") {
7101
+ const { execSync: execSync5 } = await import("child_process");
7102
+ return {
7103
+ execute: async (workingDir, args) => {
7104
+ try {
7105
+ const pattern = args.pattern || "*";
7106
+ const cmd = `ls -1 ${pattern} 2>/dev/null || echo "(no files found)"`;
7107
+ const output = execSync5(cmd, { cwd: workingDir, encoding: "utf-8", maxBuffer: 1024 * 1024 });
7108
+ return { success: true, output: output.trim() };
7109
+ } catch {
7110
+ return { success: true, output: "(no files found)" };
7111
+ }
7112
+ }
7113
+ };
7114
+ }
7115
+ if (name === "read_dir") {
7116
+ const { readdirSync: readdirSync13, existsSync: existsSync16 } = await import("fs");
7117
+ const { resolve: resolve5 } = await import("path");
7118
+ return {
7119
+ execute: async (workingDir, args) => {
7120
+ const dirPath = resolve5(workingDir, args.path || ".");
7121
+ if (!existsSync16(dirPath)) return { success: false, output: "", error: `Directory not found: ${args.path}` };
7122
+ const entries = readdirSync13(dirPath, { withFileTypes: true });
7123
+ const output = entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).join("\n");
7124
+ return { success: true, output };
7125
+ }
7126
+ };
7127
+ }
7128
+ if (name === "execute_command") {
7129
+ const { execSync: execSync5 } = await import("child_process");
7130
+ return {
7131
+ execute: async (workingDir, args) => {
7132
+ try {
7133
+ const command = args.command || "";
7134
+ const timeout = parseInt(args.timeout || "30000", 10);
7135
+ const output = execSync5(command, {
7136
+ cwd: workingDir,
7137
+ encoding: "utf-8",
7138
+ maxBuffer: 1024 * 1024,
7139
+ timeout
7140
+ });
7141
+ return { success: true, output: output.trim() };
7142
+ } catch (err) {
7143
+ const e = err;
7144
+ return { success: false, output: e.stderr || e.message, error: e.message };
7145
+ }
7146
+ }
7147
+ };
7148
+ }
7149
+ return null;
7150
+ }
6891
7151
  async function cmdTUI(options) {
7152
+ const workingDir = options.dir || process.cwd();
6892
7153
  console.log(chalk35.dim("Starting LoveCode TUI..."));
6893
7154
  if (options.theme) {
6894
7155
  const mod = await import("./theme-ZRZYRB2Q.js");
6895
7156
  const names = mod.getThemeNames();
6896
- const themeName = options.theme;
6897
- const n = themeName;
6898
- if (names.includes(themeName)) {
6899
- mod.setTheme(n);
6900
- } else {
6901
- console.log(chalk35.yellow(`Unknown theme "${options.theme}". Using default. Available: ${names.join(", ")}`));
6902
- }
7157
+ if (names.includes(options.theme)) {
7158
+ mod.setTheme(options.theme);
7159
+ }
7160
+ }
7161
+ const ai = await loadAIProvider();
7162
+ if (!ai) {
7163
+ startTUI({
7164
+ projectName: "LoveCode AI",
7165
+ branch: "main",
7166
+ fileCount: 142,
7167
+ language: "TypeScript",
7168
+ framework: "Node.js",
7169
+ repoStatus: "clean",
7170
+ messages: [],
7171
+ onSendMessage: async () => "No AI provider configured. Run `lovecode init` to set one up.",
7172
+ onRunCommand: async () => "No AI provider configured."
7173
+ });
7174
+ return;
6903
7175
  }
7176
+ const { provider, config } = ai;
7177
+ const messages = [{ role: "system", content: TOOL_SYSTEM_PROMPT }];
6904
7178
  startTUI({
6905
7179
  projectName: "LoveCode AI",
6906
7180
  branch: "main",
@@ -6909,13 +7183,32 @@ async function cmdTUI(options) {
6909
7183
  framework: "Node.js",
6910
7184
  repoStatus: "clean",
6911
7185
  messages: [],
7186
+ provider: provider.name,
7187
+ model: config.model,
6912
7188
  onSendMessage: async (msg) => {
6913
- return `You said: ${msg}
6914
-
6915
- This is a simulated AI response. Connect to a real AI provider for actual responses.`;
7189
+ messages.push({ role: "user", content: msg });
7190
+ for (let round = 0; round < 5; round++) {
7191
+ const response = await provider.chat(messages, config);
7192
+ messages.push({ role: "assistant", content: response });
7193
+ const toolOutputs = await processToolCalls(response, workingDir);
7194
+ if (toolOutputs.length === 0) {
7195
+ return response;
7196
+ }
7197
+ for (const output of toolOutputs) {
7198
+ messages.push({ role: "user", content: output });
7199
+ }
7200
+ }
7201
+ return "Max tool call rounds reached. Please try again with a simpler request.";
6916
7202
  },
6917
- onRunCommand: async (cmd) => {
6918
- return `Simulated output for: ${cmd}`;
7203
+ onRunCommand: async (command) => {
7204
+ const { execSync: execSync5 } = await import("child_process");
7205
+ try {
7206
+ const output = execSync5(command, { cwd: workingDir, encoding: "utf-8", timeout: 6e4 });
7207
+ return output.trim();
7208
+ } catch (err) {
7209
+ const e = err;
7210
+ return e.stderr || e.message;
7211
+ }
6919
7212
  }
6920
7213
  });
6921
7214
  }
@@ -6925,15 +7218,18 @@ var tuiCommand = new Command13("tui").alias("ui").description("Launch the Termin
6925
7218
  Escape Enter vim normal mode
6926
7219
  i Enter vim insert mode
6927
7220
  j/k Scroll up/down (vim normal mode)
6928
- Ctrl+N/P Next/previous pane
6929
7221
 
6930
7222
  Slash commands:
6931
7223
  /help Show help
6932
7224
  /clear Clear messages
6933
- /theme <n> Change theme (default, dark, light, ocean, solarized)
6934
- /vim Toggle vim mode
7225
+ /theme <n> Change theme
7226
+ /connect Show providers
7227
+ /model Show AI config
7228
+ /export Save chat to file
6935
7229
  /!<cmd> Run a shell command
6936
7230
  /exit Quit TUI
7231
+
7232
+ The AI can read, write, and edit files in the project directory.
6937
7233
  `);
6938
7234
 
6939
7235
  // src/commands/plugin.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lovecode-ai",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Terminal-native autonomous coding agent powered by free AI models",
5
5
  "keywords": [
6
6
  "ai",