├── .gitignore
├── README.md
├── package.json
├── Claude.mjs
├── GPT.mjs
├── holefill.mjs
├── Chat.mjs
├── refactor.mjs
├── chatsh.mjs
├── aiemu.mjs
├── kindcoder.mjs
└── KIND2_GUIDE_AI.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | ab_challenge/
3 | tmp/
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Taelin AI Scripts
2 | =================
3 |
4 | Some AI scripts I use daily.
5 |
6 | - `holefill`: I use it on VIM to fill code snippets
7 |
8 | - `aiemu`: moved to [here](https://github.com/victorTaelin/aiemu)
9 |
10 | - `chatsh [model]`: like ChatGPT but in the terminal
11 |
12 | TODO: remove `Claude.mjs`/`GPT.mjs` and just use `Ask.mjs` in all files
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "taelin-ai-scripts",
3 | "version": "0.1.0",
4 | "description": "Handy AI scripts by Taelin",
5 | "type": "module",
6 | "main": "main.js",
7 | "bin": {
8 | "holefill": "holefill.mjs",
9 | "chatsh": "chatsh.mjs",
10 | "csh": "chatsh.mjs",
11 | "refactor": "refactor.mjs",
12 | "kindcoder": "kindcoder.mjs",
13 | "aiemu": "aiemu.mjs"
14 | },
15 | "scripts": {},
16 | "keywords": [
17 | "cli",
18 | "openai",
19 | "gpt-4"
20 | ],
21 | "author": "",
22 | "license": "MIT",
23 | "dependencies": {
24 | "@anthropic-ai/sdk": "^0.19.1",
25 | "@google/generative-ai": "^0.12.0",
26 | "@portkey-ai/gateway": "^1.4.0",
27 | "gateway": "^1.0.0",
28 | "gpt-tokenizer": "^2.1.2",
29 | "groq-sdk": "^0.3.2",
30 | "openai": "^4.31.0",
31 | "portkey-ai": "^1.3.0",
32 | "punycode": "^2.3.1"
33 | },
34 | "devDependencies": {
35 | "tsx": "^4.16.2"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Claude.mjs:
--------------------------------------------------------------------------------
1 | import { Anthropic } from '@anthropic-ai/sdk';
2 | import fs from 'fs/promises';
3 | import path from 'path';
4 |
5 | async function getAnthropicKey() {
6 | const keyPath = path.join(process.env.HOME, '.config', 'anthropic.token');
7 | return (await fs.readFile(keyPath, 'utf8')).trim();
8 | }
9 |
10 | export async function ask({ system, prompt, max_tokens, model = 'claude-3-opus-20240229', temperature = 1, debug = true }) {
11 | const anthropic = new Anthropic({ apiKey: await getAnthropicKey() });
12 | if (debug) {
13 | const stream = anthropic.messages.stream({
14 | model,
15 | messages: [{ role: 'user', content: prompt }],
16 | max_tokens: max_tokens || 4096,
17 | temperature,
18 | ...(system && { system }),
19 | }).on('text', (text) => process.stdout.write(text));
20 | const message = await stream.finalMessage();
21 | console.log(); // Add a newline at the end
22 | return message.content[0].text;
23 | } else {
24 | const message = await anthropic.messages.create({
25 | model,
26 | messages: [{ role: 'user', content: prompt }],
27 | max_tokens: max_tokens || 4096,
28 | temperature,
29 | ...(system && { system }),
30 | });
31 | return message.content[0].text;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/GPT.mjs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import process from "process";
4 | import OpenAI from "openai";
5 | import fs from "fs/promises";
6 | import os from "os";
7 | import path from "path";
8 | import { encode } from "gpt-tokenizer/esm/model/davinci-codex"; // tokenizer
9 |
10 | const openai = new OpenAI({apiKey: await get_token()});
11 |
12 | export async function get_token() {
13 | const tokenPath = path.join(os.homedir(), ".config", "openai.token");
14 | try {
15 | const token = (await fs.readFile(tokenPath, "utf8")).trim();
16 | return token;
17 | } catch (err) {
18 | if (err.code === "ENOENT") {
19 | console.error("Error: openai.token file not found in `~/.config/openai.token`.");
20 | console.error("Please make sure the file exists and contains your OpenAI API token.");
21 | } else {
22 | console.error("Error reading openai.token file:", err.message);
23 | }
24 | process.exit(1);
25 | }
26 | }
27 |
28 | export async function ask({system, prompt, model, temperature}) {
29 | const stream = await openai.chat.completions.create({
30 | model: model || "gpt-4-turbo-2024-04-09",
31 | messages: [
32 | {role: "system", content: system || "You're a helpful assistant." },
33 | {role: "user", content: prompt || "What time is it?" }
34 | ],
35 | stream: true,
36 | temperature: temperature || 0,
37 | });
38 | var result = "";
39 | for await (const chunk of stream) {
40 | var text = chunk.choices[0]?.delta?.content || "";
41 | process.stdout.write(text);
42 | result += text;
43 | }
44 | process.stdout.write("\n");
45 | return result;
46 | }
47 |
48 | export function token_count(inputText) {
49 | // Encode the input string into tokens
50 | const tokens = encode(inputText);
51 |
52 | // Get the number of tokens
53 | const numberOfTokens = tokens.length;
54 |
55 | // Return the number of tokens
56 | return numberOfTokens;
57 | }
58 |
--------------------------------------------------------------------------------
/holefill.mjs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { chat, MODELS, tokenCount } from './Chat.mjs';
3 | import process from "process";
4 | import fs from 'fs/promises';
5 | import os from 'os';
6 | import path from 'path';
7 |
8 | const system = `
9 | You are a HOLE FILLER. You are provided with a file containing holes, formatted
10 | as '{{HOLE_NAME}}'. Your TASK is to complete with a string to replace this hole
11 | with, inside a XML tag, including context-aware indentation, if
12 | needed. All completions MUST be truthful, accurate, well-written and correct.
13 |
14 | ## EXAMPLE QUERY:
15 |
16 |
17 | function sum_evens(lim) {
18 | var sum = 0;
19 | for (var i = 0; i < lim; ++i) {
20 | {{FILL_HERE}}
21 | }
22 | return sum;
23 | }
24 |
25 |
26 | ## CORRECT COMPLETION
27 |
28 | if (i % 2 === 0) {
29 | sum += i;
30 | }
31 |
32 | ## EXAMPLE QUERY:
33 |
34 |
35 | def sum_list(lst):
36 | total = 0
37 | for x in lst:
38 | {{FILL_HERE}}
39 | return total
40 |
41 | print sum_list([1, 2, 3])
42 |
43 |
44 | ## CORRECT COMPLETION:
45 |
46 | total += x
47 |
48 | ## EXAMPLE QUERY:
49 |
50 |
51 | // data Tree a = Node (Tree a) (Tree a) | Leaf a
52 |
53 | // sum :: Tree Int -> Int
54 | // sum (Node lft rgt) = sum lft + sum rgt
55 | // sum (Leaf val) = val
56 |
57 | // convert to TypeScript:
58 | {{FILL_HERE}}
59 |
60 |
61 | ## CORRECT COMPLETION:
62 |
63 | type Tree
64 | = {$:"Node", lft: Tree, rgt: Tree}
65 | | {$:"Leaf", val: T};
66 |
67 | function sum(tree: Tree): number {
68 | switch (tree.$) {
69 | case "Node":
70 | return sum(tree.lft) + sum(tree.rgt);
71 | case "Leaf":
72 | return tree.val;
73 | }
74 | }
75 |
76 | ## EXAMPLE QUERY:
77 |
78 | The 2nd {{FILL_HERE}} is Saturn.
79 |
80 | ## CORRECT COMPLETION:
81 |
82 | gas giant
83 |
84 | ## EXAMPLE QUERY:
85 |
86 | function hypothenuse(a, b) {
87 | return Math.sqrt({{FILL_HERE}}b ** 2);
88 | }
89 |
90 | ## CORRECT COMPLETION:
91 |
92 | a ** 2 +
93 |
94 | ## IMPORTANT:
95 |
96 | - Answer ONLY with the block. Do NOT include anything outside it.
97 | `;
98 |
99 | var file = process.argv[2];
100 | var mini = process.argv[3];
101 | var model = process.argv[4] || "s";
102 | var ask = chat(model);
103 |
104 | if (!file) {
105 | console.log("Usage: holefill [] []");
106 | console.log("");
107 | console.log("This will replace all {{HOLES}} in , using GPT-4 / Claude-3.");
108 | console.log("A shortened file can be used to omit irrelevant parts.");
109 | process.exit();
110 | }
111 |
112 | var file_code = await fs.readFile(file, 'utf-8');
113 | var mini_code = mini ? await fs.readFile(mini, 'utf-8') : file_code;
114 |
115 | // Imports context files when //./path_to_file// is present.
116 | var regex = /\/\/\.\/(.*?)\/\//g;
117 | var match;
118 | while ((match = regex.exec(mini_code)) !== null) {
119 | var import_path = path.resolve(path.dirname(file), match[1]);
120 | if (await fs.stat(import_path).then(() => true).catch(() => false)) {
121 | var import_text = await fs.readFile(import_path, 'utf-8');
122 | console.log("import_file:", match[0]);
123 | mini_code = mini_code.replace(match[0], '\n' + import_text);
124 | } else {
125 | console.log("import_file:", match[0], "ERROR");
126 | process.exit(1);
127 | }
128 | }
129 |
130 | await fs.writeFile(mini, mini_code, 'utf-8');
131 |
132 | var tokens = tokenCount(mini_code);
133 | var holes = mini_code.match(/{{\w+}}/g) || [];
134 |
135 | if (holes.length === 0 && mini_code.indexOf("??") !== -1 && (mini_code.match(/\?\?/g) || []).length == 1) {
136 | holes = "??";
137 | }
138 |
139 | console.log("holes_found:", holes);
140 | console.log("token_count:", tokens);
141 | console.log("model_label:", MODELS[model] || model);
142 |
143 | if (holes === "??") {
144 | console.log("next_filled: ??");
145 | var prompt = "\n" + mini_code.replace("??", "{{FILL_HERE}}") + "\n";
146 | var answer = await ask(prompt, {system, model});
147 | var match = answer.match(/([\s\S]*?)<\/COMPLETION>/);
148 | if (match) {
149 | file_code = file_code.replace("??", match[1]);
150 | } else {
151 | console.error("Error: Could not find tags in the AI's response.");
152 | process.exit(1);
153 | }
154 | } else {
155 | for (let hole of holes) {
156 | console.log("next_filled: " + hole + "...");
157 | var prompt = "\n" + mini_code + "\n";
158 | var answer = await ask(prompt, {system, model});
159 | var match = answer.match(/([\s\S]*?)<\/COMPLETION>/);
160 | if (match) {
161 | file_code = file_code.replace(hole, match[1]);
162 | } else {
163 | console.error("Error: Could not find tags in the AI's response for hole: " + hole);
164 | process.exit(1);
165 | }
166 | }
167 | }
168 |
169 | await fs.writeFile(file, file_code, 'utf-8');
170 |
--------------------------------------------------------------------------------
/Chat.mjs:
--------------------------------------------------------------------------------
1 | import fs from 'fs/promises';
2 | import os from 'os';
3 | import path from 'path';
4 | import { OpenAI } from "openai";
5 | import { Anthropic } from '@anthropic-ai/sdk';
6 | import { Groq } from "groq-sdk";
7 | import { GoogleGenerativeAI } from "@google/generative-ai";
8 | import { encode } from "gpt-tokenizer/esm/model/davinci-codex"; // tokenizer
9 |
10 | // Map of model shortcodes to full model names
11 | export const MODELS = {
12 | g: 'gpt-4o',
13 | G: 'gpt-4-32k-0314',
14 | h: 'claude-3-haiku-20240307',
15 | s: 'claude-3-5-sonnet-20240620',
16 | o: 'claude-3-opus-20240229',
17 | l: 'llama3-8b-8192',
18 | L: 'llama3-70b-8192',
19 | i: 'gemini-1.5-flash-latest',
20 | I: 'gemini-1.5-pro-latest'
21 | };
22 |
23 | // Factory function to create a stateful OpenAI chat
24 | export function openAIChat(clientClass) {
25 | const messages = [];
26 |
27 | async function ask(userMessage, { system, model, temperature = 0.0, max_tokens = 4096, stream = true }) {
28 | model = MODELS[model] || model;
29 | const client = new clientClass({ apiKey: await getToken(clientClass.name.toLowerCase()) });
30 |
31 | if (messages.length === 0) {
32 | messages.push({ role: "system", content: system });
33 | }
34 |
35 | messages.push({ role: "user", content: userMessage });
36 |
37 | const params = { messages, model, temperature, max_tokens, stream };
38 |
39 | let result = "";
40 | const response = await client.chat.completions.create(params);
41 |
42 | for await (const chunk of response) {
43 | const text = chunk.choices[0]?.delta?.content || "";
44 | process.stdout.write(text);
45 | result += text;
46 | }
47 |
48 | messages.push({ role: 'assistant', content: result });
49 |
50 | return result;
51 | }
52 |
53 | return ask;
54 | }
55 |
56 | // Factory function to create a stateful Anthropic chat
57 | export function anthropicChat(clientClass) {
58 | const messages = [];
59 |
60 | async function ask(userMessage, { system, model, temperature = 0.0, max_tokens = 4096, stream = true }) {
61 | model = MODELS[model] || model;
62 | const client = new clientClass({ apiKey: await getToken(clientClass.name.toLowerCase()) });
63 |
64 | messages.push({ role: "user", content: userMessage });
65 |
66 | const params = { system, model, temperature, max_tokens, stream };
67 |
68 | let result = "";
69 | const response = client.messages
70 | .stream({ ...params, messages })
71 | .on('text', (text) => {
72 | process.stdout.write(text);
73 | result += text;
74 | });
75 | await response.finalMessage();
76 |
77 | messages.push({ role: 'assistant', content: result });
78 |
79 | return result;
80 | }
81 |
82 | return ask;
83 | }
84 |
85 | export function geminiChat(clientClass) {
86 | const messages = [];
87 |
88 | async function ask(userMessage, { system, model, temperature = 0.0, max_tokens = 4096, stream = true }) {
89 | model = MODELS[model] || model;
90 | const client = new clientClass(await getToken(clientClass.name.toLowerCase()));
91 |
92 | const generationConfig = {
93 | maxOutputTokens: max_tokens,
94 | temperature,
95 | };
96 |
97 | const chat = client.getGenerativeModel({ model, systemInstruction: system, generationConfig })
98 | .startChat({ history: messages });
99 |
100 | messages.push({ role: "user", parts: [{ text: userMessage }] });
101 |
102 | let result = "";
103 | if (stream) {
104 | const response = await chat.sendMessageStream(userMessage);
105 | for await (const chunk of response.stream) {
106 | const text = chunk.text();
107 | process.stdout.write(text);
108 | result += text;
109 | }
110 | } else {
111 | const response = await chat.sendMessage(userMessage);
112 | result = (await response.response).text();
113 | }
114 |
115 | messages.push({ role: 'model', parts: [{ text: result }] });
116 |
117 | return result;
118 | }
119 |
120 | return ask;
121 | }
122 |
123 | // Generic asker function that dispatches to the correct asker based on the model name
124 | export function chat(model) {
125 | model = MODELS[model] || model;
126 | if (model.startsWith('gpt')) {
127 | return openAIChat(OpenAI);
128 | } else if (model.startsWith('claude')) {
129 | return anthropicChat(Anthropic);
130 | } else if (model.startsWith('llama')) {
131 | return openAIChat(Groq);
132 | } else if (model.startsWith('gemini')) {
133 | return geminiChat(GoogleGenerativeAI);
134 | } else {
135 | throw new Error(`Unsupported model: ${model}`);
136 | }
137 | }
138 |
139 | // Utility function to read the API token for a given vendor
140 | async function getToken(vendor) {
141 | const tokenPath = path.join(os.homedir(), '.config', `${vendor}.token`);
142 | try {
143 | return (await fs.readFile(tokenPath, 'utf8')).trim();
144 | } catch (err) {
145 | console.error(`Error reading ${vendor}.token file:`, err.message);
146 | process.exit(1);
147 | }
148 | }
149 |
150 | export function tokenCount(inputText) {
151 | // Encode the input string into tokens
152 | const tokens = encode(inputText);
153 |
154 | // Get the number of tokens
155 | const numberOfTokens = tokens.length;
156 |
157 | // Return the number of tokens
158 | return numberOfTokens;
159 | }
160 |
--------------------------------------------------------------------------------
/refactor.mjs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import fs from 'fs/promises';
4 | import os from 'os';
5 | import path from 'path';
6 | import process from "process";
7 | import { chat, MODELS, tokenCount } from './Chat.mjs';
8 | import { exec } from 'child_process';
9 | import { promisify } from 'util';
10 |
11 | const execAsync = promisify(exec);
12 |
13 | // System prompt for the AI model, defining its role and behavior
14 | const system = `
15 | You are a file refactoring tool.
16 |
17 | - INPUT: You will receive a FILE and a change REQUEST.
18 |
19 | - OUTPUT: You must answer with the changed file inside tags.
20 |
21 | # GUIDE FOR REFACTORING
22 |
23 | 1. Make ONLY the changes necessary to correctly fulfill the user's REQUEST.
24 | 2. Do NOT fix, remove, complete, or alter any parts unrelated to the REQUEST.
25 | 3. Do not include any additional comments, explanations, or text outside of the RESULT tags.
26 | 4. NEVER assume information you don't have. ALWAYS request files to make sure.
27 | 5. Preserve the same indentation and style of the current file.
28 | 6. Be precise and careful in your modifications.
29 |
30 | # GUIDE FOR NAVIGATING
31 |
32 | In some cases, you WILL need additional context to fulfill a request. When that is the case, do NOT attempt to refactor the file immediatelly. Instead, ask for additional files inside tags, as follows:
33 |
34 |
35 | ["./README.md", "./src/some_file.js", "./src/some_dir"]
36 |
37 |
38 | You can ask for information as many times as you want.
39 |
40 | # GUIDE FOR AGDA STYLE
41 |
42 | Every Agda file MUST:
43 |
44 | 1. Start with a "module" declaration.
45 |
46 | 2. Must have ONLY ONE top-level definition.
47 |
48 | 3. Export ALL its in-scope values>
49 |
50 | Example file:
51 |
52 | \`\`\`
53 | module Term where
54 |
55 | {-# NO_TERMINATION_CHECK #-}
56 |
57 | data Term : Set where
58 | var : Term
59 | abs : (Term → Term) → Term
60 | app : Term → Term → Term
61 |
62 | open Term public using (Term; var; abs; app)
63 | \`\`\`
64 |
65 | # EXAMPLE
66 |
67 | Below is a complete example of how you should interact with the user.
68 |
69 | ## User:
70 |
71 |
72 | Bool.agda
73 | Nat.agda
74 | is_zero.agda
75 |
76 |
77 |
78 | module is_zero where
79 | open import Nat
80 | open import Bool
81 |
82 | is_zero : Nat -> Bool
83 | is_zero n = ?
84 |
85 |
86 |
87 | case-split on n
88 |
89 |
90 | ## You:
91 |
92 |
93 | ["./Nat.agda"]
94 |
95 |
96 | ## User:
97 |
98 |
99 | module Nat where
100 |
101 | data Nat : Set where
102 | zero : Nat
103 | succ : Nat → Nat
104 |
105 | open Nat public using (Nat; zero; succ)
106 |
107 |
108 | ## You:
109 |
110 |
111 | module is_zero where
112 |
113 | open import Nat
114 | open import Bool
115 |
116 | is_zero : Nat -> Bool
117 | is_zero zero = ?
118 | is_zero (succ n) = ?
119 |
120 |
121 | ## User:
122 |
123 |
124 | complete it
125 |
126 |
127 | ## You:
128 |
129 |
130 | ["/.Bool.agda"]
131 |
132 |
133 | ## User:
134 |
135 |
136 | module Bool where
137 |
138 | data Bool : Set where
139 | true : Bool
140 | false : Bool
141 |
142 | open Bool public using (Bool; true; false)
143 |
144 |
145 | ## You:
146 |
147 |
148 | module is_zero where
149 |
150 | open import Nat
151 | open import Bool
152 |
153 | is_zero : Nat -> Bool
154 | is_zero zero = true
155 | is_zero (succ n) = false
156 |
157 | `;
158 |
159 | // Main function to handle the refactoring process
160 | async function main() {
161 | // Check for correct usage and parse command-line arguments
162 | if (process.argv.length < 3) {
163 | console.log("Usage: refactor [] [--check]");
164 | process.exit(1);
165 | }
166 |
167 | const file = process.argv[2];
168 | const request = process.argv[3];
169 | const model = process.argv[4] || "s";
170 | const check = process.argv.includes("--check");
171 |
172 | // Initialize the chat function with the specified model
173 | const ask = chat(model);
174 |
175 | // Get directory and file information
176 | const dir = path.dirname(file);
177 | const fileContent = await fs.readFile(file, 'utf-8');
178 | const dirContent = await fs.readdir(dir);
179 |
180 | // Prepare initial input for the AI
181 | let aiInput = `\n${dirContent.join('\n')}\n\n\n\n${fileContent}\n\n\n\n${request}\n`;
182 |
183 | // If --check flag is present, perform initial type check
184 | if (check) {
185 | const initialCheck = await typeCheck(file);
186 | aiInput += `\n\n\n${initialCheck || 'No errors.'}\n`;
187 | }
188 |
189 | // Main interaction loop with the AI
190 | while (true) {
191 | console.log("");
192 | const aiOutput = await ask(aiInput, { system, model });
193 |
194 | // Handle AI's request for additional information
195 | if (aiOutput.includes("")) {
196 | const showMatch = aiOutput.match(/([\s\S]*?)<\/SHOW>/);
197 | if (showMatch) {
198 | const filesToShow = JSON.parse(showMatch[1]);
199 | let showContent = "";
200 | for (const fileToShow of filesToShow) {
201 | const fullPath = path.resolve(dir, fileToShow);
202 | if (await fs.stat(fullPath).then(stat => stat.isDirectory())) {
203 | const dirContent = await fs.readdir(fullPath);
204 | showContent += `\n${dirContent.join('\n')}\n\n`;
205 | } else {
206 | const content = await fs.readFile(fullPath, 'utf-8');
207 | showContent += `\n${content}\n\n`;
208 | }
209 | }
210 | aiInput = showContent;
211 | }
212 | }
213 | // Handle AI's refactoring result
214 | else if (aiOutput.includes("")) {
215 | const resultMatch = aiOutput.match(/([\s\S]*?)<\/RESULT>/);
216 | if (resultMatch) {
217 | const newContent = resultMatch[1];
218 | await fs.writeFile(file, newContent.trim(), 'utf-8');
219 | console.log("\nFile refactored successfully.");
220 |
221 | // If --check flag is present, perform type check on the refactored file
222 | if (check) {
223 | const checkResult = await typeCheck(file);
224 | if (checkResult) {
225 | aiInput = `\n${newContent.trim()}\n\n\n\nFix this file.\n\n\n\n${checkResult}\n`;
226 | continue;
227 | }
228 | }
229 | break;
230 | }
231 | }
232 | }
233 | }
234 |
235 | // Function to perform type checking based on file extension
236 | async function typeCheck(file) {
237 | const ext = path.extname(file);
238 | let cmd;
239 | switch (ext) {
240 | case '.agda':
241 | cmd = `agda-check ${file}`;
242 | break;
243 | case '.kind2':
244 | cmd = `kind2 check ${file}`;
245 | break;
246 | case '.c':
247 | cmd = `gcc -fsyntax-only ${file}`;
248 | break;
249 | case '.ts':
250 | cmd = `tsc --noEmit ${file}`;
251 | break;
252 | case '.hs':
253 | cmd = `ghc -fno-code ${file}`;
254 | break;
255 | default:
256 | return null;
257 | }
258 |
259 | try {
260 | var result = await execAsync(cmd);
261 | return result.stderr || result.stdout;
262 | } catch (error) {
263 | return error.stderr;
264 | }
265 | }
266 |
267 | // Run the main function and handle any errors
268 | main().catch(console.error);
269 |
--------------------------------------------------------------------------------
/chatsh.mjs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import readline from 'readline';
4 | import { exec } from 'child_process';
5 | import { promisify } from 'util';
6 | import { chat, MODELS } from './Chat.mjs';
7 |
8 | const execAsync = promisify(exec);
9 |
10 | // Default model if not specified
11 | const DEFAULT_MODEL = "s";
12 | // Get model from environment variable or use default
13 | const MODEL = process.argv[2] || DEFAULT_MODEL;
14 |
15 | console.log(`Welcome to ChatSH. Model: ${MODELS[MODEL]||MODEL}\n`);
16 |
17 | // System prompt to set the assistant's behavior
18 | const SYSTEM_PROMPT = `You are ChatSH, an AI language model that specializes in assisting users with tasks on their system using shell commands. ChatSH operates in two modes: COMMAND MODE and CHAT MODE.
19 |
20 | # GUIDE for COMMAND NODE:
21 |
22 | 1. The USER asks you to perform a SYSTEM TASK.
23 |
24 | 2. ChatSH answers with a SHELL SCRIPT to perform the task.
25 |
26 | # GUIDE for CHAT MODE:
27 |
28 | 1. The USER asks an ARBITRARY QUESTION or OPEN-ENDED MESSAGE.
29 |
30 | 2. ChatSH answers it with a concise, factual response.
31 |
32 | # EXAMPLE 1:
33 |
34 |
35 | Show me local files.
36 |
37 |
38 |
39 | \`\`\`sh
40 | ls
41 | \`\`\`
42 |
43 |
44 |
45 | example.gif example.tst example_dir/
46 |
47 |
48 |
49 | Is there any text file?
50 |
51 |
52 |
53 | Yes, there is a file named example.txt in the current directory
54 |
55 |
56 |
57 | Show me the contents of example.txt.
58 |
59 |
60 |
61 | cat example.txt
62 |
63 |
64 |
65 | Na matemática, um grupo é um conjunto de elementos associados a uma operação que combina dois elementos quaisquer para formar um terceiro. Para se qualificar como grupo o conjunto e a operação devem satisfazer algumas condições chamadas axiomas de grupo: associatividade, elemento neutro e elementos inversos.
66 |
67 |
68 |
69 | Can you translate the first sentence to English?
70 |
71 |
72 |
73 | In mathematics, a group is a set of elements associated with an operation that combines any two elements to form a third element.
74 |
75 |
76 |
77 | What is the monster group?
78 |
79 |
80 |
81 | The monster group is the largest sporadic simple group, with order approximately 8×10^53.
82 |
83 |
84 |
85 | What is a simple group?
86 |
87 |
88 |
89 | A simple group is a nontrivial group that contains no proper nontrivial normal subgroups.
90 |
91 |
92 |
93 | Move the text file to that dir.
94 |
95 |
96 |
97 | \`\`\`sh
98 | mv example.txt example_dir/
99 | \`\`\`
100 |
101 |
102 | # EXAMPLE 2:
103 |
104 |
105 | Fix main.ts
106 |
107 |
108 |
109 | \`\`\`sh
110 | cat main.ts
111 | tsc --noEmit main.ts
112 | \`\`\`
113 |
114 |
115 |
116 | import List from './list';
117 | import map from './map';
118 |
119 | const n_1_2: List = { $: "cons", head: 1, tail: { $: "cons", head: 2, tail: { $: "nil" }}};
120 | const n_2_4: List = map(n_1_2, x => x * 2);
121 |
122 | console.log(JSON.stringify(n_2_4));
123 |
124 | map.ts:7:22 - error TS2345: Argument of type '(val: T) => U' is not assignable to parameter of type 'List'.
125 |
126 | 7 let tail = map(fun, list.tail);
127 | ~~~
128 |
129 |
130 |
131 | \`\`\`sh
132 | ls
133 | \`\`\`
134 |
135 |
136 |
137 | list.ts main.ts map.ts
138 |
139 |
140 |
141 | \`\`\`sh
142 | cat map.ts
143 | \`\`\`
144 |
145 |
146 |
147 | import List from './list';
148 |
149 | function map(list: List, fun: (val: T) => U): List {
150 | switch (list.$) {
151 | case "cons":
152 | let head = fun(list.head);
153 | let tail = map(fun, list.tail);
154 | return { $: "cons", head, tail };
155 | case "nil":
156 | return { $: "nil" };
157 | }
158 | }
159 |
160 | export default map;
161 |
162 |
163 |
164 | \`\`\`sh
165 | cat > map.ts << EOL
166 | import List from './list';
167 |
168 | function map(list: List, fun: (val: T) => U): List {
169 | switch (list.$) {
170 | case "cons":
171 | let head = fun(list.head);
172 | let tail = map(list.tail, fun);
173 | return { $: "cons", head, tail };
174 | case "nil":
175 | return { $: "nil" };
176 | }
177 | }
178 |
179 | export default map;
180 | EOL
181 | tsc --noEmit map.ts
182 | \`\`\`
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 | Done.
191 | {
231 | rl.question(query, resolve);
232 | });
233 | }
234 |
235 | // If there are words after the 'chatsh', set them as the initialUserMessage
236 | var initialUserMessage = process.argv.slice(3).join(' ');
237 |
238 | // Main interaction loop
239 | async function main() {
240 | let lastOutput = "";
241 |
242 | while (true) {
243 | let userMessage;
244 | if (initialUserMessage) {
245 | userMessage = initialUserMessage;
246 | initialUserMessage = null;
247 | } else {
248 | process.stdout.write('\x1b[1m'); // blue color
249 | userMessage = await prompt('λ ');
250 | process.stdout.write('\x1b[0m'); // reset color
251 | }
252 |
253 | try {
254 | const fullMessage = userMessage.trim() !== ''
255 | ? `\n${lastOutput.trim()}\n\n\n${userMessage}\n\n`
256 | : `\n${lastOutput.trim()}\n`;
257 |
258 | const assistantMessage = await ask(fullMessage, { system: SYSTEM_PROMPT, model: MODEL });
259 | console.log();
260 |
261 | const code = extractCode(assistantMessage);
262 | lastOutput = "";
263 |
264 | if (code) {
265 | console.log("\x1b[31mPress enter to execute, or 'N' to cancel.\x1b[0m");
266 | const answer = await prompt('');
267 | // TODO: delete the warning above from the terminal
268 | process.stdout.moveCursor(0, -2);
269 | process.stdout.clearLine(2);
270 | if (answer.toLowerCase() === 'n') {
271 | console.log('Execution skipped.');
272 | lastOutput = "Command skipped.\n";
273 | } else {
274 | try {
275 | const {stdout, stderr} = await execAsync(code);
276 | const output = `${stdout.trim()}${stderr.trim()}`;
277 | console.log('\x1b[2m' + output.trim() + '\x1b[0m');
278 | lastOutput = output;
279 | } catch(error) {
280 | const output = `${error.stdout?.trim()||''}${error.stderr?.trim()||''}`;
281 | console.log('\x1b[2m' + output.trim() + '\x1b[0m');
282 | lastOutput = output;
283 | }
284 | }
285 | }
286 | } catch(error) {
287 | console.error(`Error: ${error.message}`);
288 | }
289 | }
290 | }
291 |
292 | // Utility function to extract code from the assistant's message
293 | function extractCode(text) {
294 | const match = text.match(/```sh([\s\S]*?)```/);
295 | return match ? match[1].trim() : null;
296 | }
297 |
298 | async function get_shell() {
299 | const shellInfo = (await execAsync('uname -a && $SHELL --version')).stdout.trim();
300 | return shellInfo;
301 | }
302 |
303 | main();
304 |
--------------------------------------------------------------------------------
/aiemu.mjs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import process from "process";
4 | import fs from 'fs/promises';
5 | import { chat, MODELS } from './Chat.mjs';
6 |
7 | const MODEL = process.argv[2] || "s";
8 |
9 | const SYSTEM = `
10 | You're a game emulator. You can emulate ANY game, but text-based. Your goal is
11 | to be a fully playable text-based version of the game, emulating as close to the
12 | original as possible, from start to end.
13 |
14 | You'll be provided with:
15 | 1. The chosen game.
16 | 2. The current message context.
17 |
18 | Your responses must include:
19 | 1. A short description of the current game screen or state.
20 | 2. A textual 2D UI of the current game screen, using emojis and symbols.
21 | 3. A labelled list of options that the player can take.
22 |
23 | Always follow this template:
24 |
25 | <>
26 | <>
27 | <>
28 |
29 | Guidelines for the game screen UI:
30 | - Draw it as compactly as possible while maintaining readability.
31 | - When handy, add a description / narration above the screen.
32 | - Use a 2D textual grid to position key game elements spatially.
33 | - Represent sprites, characters, items etc with 1-3 emojis each.
34 | - Draw HP/mana bars, item counts etc visually to form a nice UI.
35 | - Use ASCII diagrams very sparingly, mainly for progress bars.
36 | - Include menu options like Pause, Inventory etc for completeness.
37 | - Expand item/action options (e.g. Use X, Attack, Defend) for faster play.
38 |
39 | Here are some examples of how your game screen should look.
40 |
41 | //# Example: Pokémon Red - Battle Screen
42 |
43 | You're in a Pokémon battle.
44 | ,-----------------------------,
45 | Blastoise LV30 [💦🐢💣]
46 | HP: |||....... [🔫🐚🛡️]
47 |
48 | Charizard LV32 [🔥🐉🦇]
49 | HP: ||||||.... [🌋🦖😤]
50 | '-----------------------------'
51 | A) FIGHT
52 | B) PKMN
53 | C) ITEM
54 | D) RUN
55 |
56 | //# Example: Zelda Majora's Mask - Odolwa Boss Room
57 |
58 | You're in Odolwa's boss room in Woodfall Temple.
59 | Odolwa is dancing and swinging his swords menacingly.
60 | ,--------------------------------------------------,
61 | HP ❤️ ❤️ ❤️ 🤍🤍🤍🤍
62 | MANA 🟩🟩🟩⬜⬜⬜⬜⬜⬜⬜
63 |
64 | Link Navi Door0
65 | [🗡️🧝🛡️] [🧚] [🚪🔒]
66 |
67 | Odolwa Jar Door1 Chest
68 | [🗡️🎭🗡️] [🏺] [🚪🔒] [🎁🔒]
69 |
70 | Grs0 Grs1 Grs2
71 | [🌿] [🌿] [🌿]
72 |
73 | 💎 000 🕒 7 AM :: ☀️ 1st Day
74 | '--------------------------------------------------'
75 | A) Talk to Navi
76 | B) Enter Door0
77 | C) Attack Odolwa
78 | D) Break the Jar
79 | E) Enter Door1
80 | F) Check Grs0
81 | G) Check Grs1
82 | H) Check Grs2
83 |
84 | //# Example: Mario 64 - Inside Castle
85 |
86 | You're in the main entrance hall of Princess Peach's castle.
87 | ,---------------------------------.
88 | 🍄x4 🌟x7
89 |
90 | Door0 Door1 Door2
91 | [🚪🌟] [🚪🔒] [🚪0]
92 |
93 | Door3 Door4 Door5 Door6
94 | [🚪0] [🚪3] [🚪7] [🚪1]
95 |
96 | Exit Mario Coin0 Coin1
97 | [🚪] [🍄] [🟡] [🟡]
98 | '---------------------------------'
99 | A) Enter Door0
100 | B) Enter Door1
101 | C) Enter Door2
102 | D) Enter Door3
103 | E) Enter Door4
104 | F) Enter Door5
105 | G) Enter Door6
106 | H) Check Coin0
107 | I) Check Coin1
108 | J) Exit
109 |
110 | //# Example: Pokémon Red - Title Screen
111 |
112 | ,-------------------------------,
113 | Pokémon
114 | Red
115 |
116 | [🔥🐉🦇]
117 |
118 | ©1996 Nintendo
119 | Creatures Inc.
120 | GAME FREAK inc.
121 |
122 | Press Start Button
123 | '-------------------------------'
124 | A) New Game
125 | B) Continue
126 | C) Options
127 |
128 | //# Example: Pokémon Red - Introduction
129 |
130 | ,-------------------------------.
131 |
132 | OAK
133 | Hello there! Welcome to the
134 | world of POKÉMON!
135 |
136 | OAK
137 | My name is OAK!
138 | People call me the
139 | POKÉMON PROF!
140 |
141 | NIDORAN♂
142 | [🐭💜🦏]
143 | '-------------------------------'
144 | A) Next
145 |
146 | //# Example: Pokémon Red - Pallet Town
147 |
148 | You're in Pallet Town, your hometown.
149 | ,--------------------------,
150 | 🌳 [Route 1] 🌳
151 |
152 | House0 House1
153 | [🏠] [🏠]
154 |
155 | Grass Oak's Lab
156 | [🌿] [🏫]
157 |
158 | Beach Sign 🌸
159 | [🌊] [🪧] 🌼
160 | '--------------------------'
161 | A) Enter House0
162 | B) Enter House1
163 | C) Enter Oak's Lab
164 | D) Check the Sign
165 | E) Walk in the Grass
166 | F) Exit to Route 1
167 |
168 | //# Example: Pokémon Red - Protagonist's House
169 |
170 | You're inside your house in Pallet Town.
171 | ,---------------------------.
172 | PC TV Stairs
173 | [💻] [📺] [┗┓]
174 |
175 | Bed You
176 | [🛏️] [👦]
177 | '---------------------------'
178 | A) Check the PC
179 | B) Play SNES on TV
180 | C) Rest in Bed
181 | B) Go Downstairs
182 |
183 | //# Example: The Legend of Zelda - Majora's Mask - Title Screen
184 |
185 | ,------------------------------------------,
186 |
187 | The Legend of
188 | Zelda
189 | Majora's Mask
190 |
191 | [🎭😈🌙]
192 |
193 | Press Start
194 |
195 |
196 | ©2000 Nintendo. All Rights Reserved.
197 | '------------------------------------------'
198 | A) PRESS START
199 | B) OPTIONS
200 |
201 | IMPORTANT:
202 | - You ARE the videogame. Stay in character.
203 | - Start from the game's initial menus and emulate each level in order.
204 | - Emulate the game loyally, following its original sequence of events.
205 | - Design a well-aligned UI for each screen. Position elements in 2D.
206 | - Respond with ONLY the next emulation step and its options.
207 | - BE CREATIVE. Make this a great, entertaining experience.
208 |
209 | If the player provides feedback after a '#', use it to improve the experience.
210 | `;
211 |
212 | (async () => {
213 |
214 | // TODO: wait for 100ms
215 | await new Promise(resolve => setTimeout(resolve, 100));
216 |
217 | console.clear();
218 |
219 | const ASCII_ART = `
220 | \x1b[1m\x1b[36m█▀▀▀▀▀█ ▀ ▄▀▄ █▀▀▀▀▀█\x1b[0m
221 | \x1b[1m\x1b[36m█ ███ █ ▀ ▀█▀ █ ███ █\x1b[0m
222 | \x1b[1m\x1b[36m█ ▀▀▀ █ █ ▄█▄ █ ▀▀▀ █\x1b[0m
223 | \x1b[1m\x1b[36m▀▀▀▀▀▀▀ ▀ ▀▀▀ ▀▀▀▀▀▀▀\x1b[0m
224 | \x1b[2mA I E M U L A T O R\x1b[0m
225 | `.trim();
226 |
227 | console.log(ASCII_ART);
228 |
229 | console.log("");
230 | console.log(`\x1b[32mUsing \x1b[1m${MODELS[MODEL]||MODEL}\x1b[0m`);
231 | console.log("");
232 |
233 | process.stdout.write("Game: ");
234 | const game = (await new Promise(resolve => process.stdin.once('data', data => resolve(data.toString())))).trim();
235 |
236 | console.log(`Emulating ${game}...\n\n`);
237 |
238 | const ask = chat(MODEL);
239 | let messages = [
240 | {role: "user", content: `# GAME: ${game}`},
241 | ];
242 |
243 | while (true) {
244 | console.clear();
245 |
246 | const response = await ask(messages[messages.length - 1].content, {
247 | system: SYSTEM,
248 | model: MODEL,
249 | max_tokens: 2048,
250 | temperature: 0.5,
251 | });
252 |
253 | messages.push({role: "assistant", content: response});
254 |
255 | process.stdout.write("\n\nEnter your choice: ");
256 | const choice = (await new Promise(resolve => process.stdin.once('data', data => resolve(data.toString())))).trim();
257 | messages.push({role: "user", content: choice});
258 |
259 | await fs.writeFile("./log.txt", messages.map(m => `${m.role === "user" ? "# PLAYER" : "# EMULATOR"}:\n\n${m.content}\n\n`).join(""));
260 | }
261 | })();
262 |
263 |
--------------------------------------------------------------------------------
/kindcoder.mjs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import fs from 'fs/promises';
4 | import os from 'os';
5 | import path from 'path';
6 | import process from "process";
7 | import { chat, MODELS, tokenCount } from './Chat.mjs';
8 | import { exec } from 'child_process';
9 | import { promisify } from 'util';
10 |
11 | const execAsync = promisify(exec);
12 | const kind2_guide = await fs.readFile(new URL('./KIND2_GUIDE_AI.md', import.meta.url), 'utf-8');
13 |
14 | // System prompt for the AI model, defining its role and behavior
15 | const system_KindCoder = `
16 | You are KindCoder, a Kind2-Lang coding assistant.
17 |
18 | # USER INPUT
19 |
20 | You will receive:
21 |
22 | 1. A target in the Kind2 language. That's the code you must update.
23 |
24 | 2. The user's change . You must perform that change on the target file.
25 |
26 | 3. Some additional context (files, dirs) that could be helpful.
27 |
28 | # KINDCODER OUTPUT
29 |
30 | You, KindCoder, must answer with a single tag, which must include the
31 | user's file, except *modified* to fulfill the user's request, and nothing else.
32 |
33 | # GUIDE FOR REFACTORING
34 |
35 | 1. Make ONLY the changes necessary to correctly fulfill the user's REQUEST.
36 | 2. Do NOT fix, remove, complete, or alter any parts unrelated to the REQUEST.
37 | 3. Preserve the same indentation and style of the target FILE.
38 | 4. Consult Kind2's guide to emit syntactically correct code.
39 | 5. Be precise and careful in your modifications.
40 |
41 | ${kind2_guide}
42 |
43 | # KINDCODER EXAMPLE
44 |
45 | Below is a complete example of how KindCoder should interact with the user.
46 |
47 | ## User:
48 |
49 |
50 | ...
51 |
52 |
53 |
54 | ...
55 |
56 |
57 |
58 | ...
59 |
60 |
61 |
62 | use Nat/{succ,zero}
63 |
64 | is_even
65 | - n: Nat
66 | : Nat
67 |
68 | ?a
69 |
70 |
71 |
72 | GOAL ?a : Nat
73 | - n: Nat
74 |
75 |
76 |
77 | case-split on n
78 |
79 |
80 | ## KindCoder:
81 |
82 |
83 | use Nat/{succ,zero}
84 |
85 | is_even
86 | - n: Nat
87 | : Nat
88 |
89 | match n {
90 | zero: ?zero_case
91 | succ: ?succ_case
92 | }
93 |
94 |
95 | # EXPLANATION
96 |
97 | ## Input:
98 |
99 | The user provided a target file (Nat/is_even) to be modified, and a request:
100 | "case-split on n". The user also provided some additional files and dirs for
101 | context (including Nat, Nat/succ, Nat/zero). The target file had an incomplete
102 | top-level definition, 'is_even', with a hole, '?a', as its body.
103 |
104 | ## Output:
105 |
106 | As a response, you, KindCoder, performed a case-split on 'n', by using Kind's
107 | 'match' syntax-sugar. You did NOT perform any extra work, nor change anything
108 | beyond what the user explicitly asked for. Instead, you just placed NEW holes
109 | ('?n_is_zero'/'?n_is_succ') on the respective cases. You included the updated
110 | file inside a RESULT tag, completing the task successfully. Good job!
111 |
112 | # TASK
113 |
114 | The user will now give you a Kind2 file, and a change request. Read it carefully
115 | and update it as demanded. Consult the guides above as necessary. Pay attention
116 | to syntax details, like mandatory parenthesis, to emit valid code. Do it now:
117 | `.trim();
118 |
119 | const system_DepsPredictor = `
120 | # ABOUT KIND2
121 |
122 | Kind2 is a minimal purely functional programming language, where every file
123 | defines exactly ONE function, type or constant. For example:
124 |
125 | '''
126 | // Nat/add.kind2: defines Nat addition
127 |
128 | use Nat/{succ,zero}
129 |
130 | add
131 | - a: Nat
132 | - b: Nat
133 | : Nat
134 |
135 | match a {
136 | succ: (succ (add a.pred b))
137 | zero: b
138 | }
139 | '''
140 |
141 | The file above implements the global 'Nat/add' definition.
142 |
143 | # INPUT
144 |
145 | You will be given the NAME of a Kind2 file, its source code (which may be
146 | empty), and a list of ALL Kind2 definitions available in the stdlib.
147 |
148 | # OUTPUT
149 |
150 | You must answer with a list of definitions that are, or that you predict WILL BE
151 | used, directly or not, inside that Kind2 file. Answer in a tag.
152 |
153 | Optionally, you can also include a SHORT, 1-paragraph .
154 |
155 | # EXAMPLE INPUT
156 |
157 | Nat/equal
158 |
159 |
160 |
161 |
162 |
163 | - List/
164 | - cons
165 | - nil
166 | - match
167 | - map
168 | - fold
169 | - filter
170 | - equal
171 | - zip
172 | - length
173 | - Nat/
174 | - match
175 | - fold
176 | - succ
177 | - zero
178 | - compare
179 | - add
180 | - sub
181 | - mul
182 | - div
183 | - mod
184 | - pow
185 | - lte
186 | - gte
187 | - Bool/
188 | - match
189 | - fold
190 | - true
191 | - false
192 | - not
193 | - and
194 | - or
195 | - xor
196 | - nand
197 |
198 |
199 | # EXAMPLE OUTPUT
200 |
201 |
202 | Nat/equal is likely to be a pairwise comparison between Nats. As such, it must
203 | include Nat (obviously), as well as its constructor and match. It returns a
204 | Bool, so, it must also include its constructors and match. For completion, I've
205 | also added bool AND and OR, since these are often used in comparison. Finally,
206 | Nat/compare and List/equal might be similar algorithms, so, I included them.
207 |
208 |
209 | Nat
210 | Nat/succ
211 | Nat/zero
212 | Nat/match
213 | Bool
214 | Bool/true
215 | Bool/false
216 | Bool/match
217 | Bool/and
218 | Bool/or
219 | Nat/compare
220 | List/equal
221 |
222 |
223 | # HINTS
224 |
225 | - Attempt to include ALL files that might be relevant, directly or not.
226 |
227 | - Always include files that might be similar algorithms to the current one.
228 | Example: 'Map/set' MUST include 'Mat/get'
229 |
230 | - If the file is the constructor of an ADT, then, INCLUDE its type.
231 | Example: 'List/cons' MUST include 'List'
232 |
233 | - When in doubt, prefer to include MORE, rather than LESS, potencial dependencies.
234 |
235 | - Try to include AT LEAST 4 dependencies, and AT MOST (only if needed) 16.
236 |
237 | - Sometimes the user will give hints in the file. Follow them.
238 | `.trim();
239 |
240 | // Function to predict dependencies
241 | async function predictDependencies(name, fileContent) {
242 | // Function to get all Kind2 files recursively
243 | async function getAllKind2Files(dir) {
244 | const entries = await fs.readdir(dir, { withFileTypes: true });
245 | const files = await Promise.all(entries.map(async (entry) => {
246 | const res = path.resolve(dir, entry.name);
247 | if (entry.isDirectory()) {
248 | const subFiles = await getAllKind2Files(res);
249 | return subFiles.length > 0 ? { name: entry.name, children: subFiles } : null;
250 | } else if (entry.name.endsWith('.kind2')) {
251 | return { name: entry.name.replace('.kind2', '') };
252 | }
253 | return null;
254 | }));
255 | return files.filter(file => file !== null).map(file => ({...file, name: file.name.replace(/\/_$/, '')}));
256 | }
257 |
258 | // Function to build a tree structure from files
259 | function buildTree(files, prefix = '') {
260 | let result = '';
261 | for (const file of files) {
262 | if (file.children) {
263 | result += `${prefix}- ${file.name}/\n`;
264 | result += buildTree(file.children, `${prefix} `);
265 | } else {
266 | result += `${prefix}- ${file.name}\n`;
267 | }
268 | }
269 | return result;
270 | }
271 |
272 | const allFiles = await getAllKind2Files("./");
273 | const defsTree = buildTree(allFiles);
274 |
275 | const aiInput = [
276 | `${name}`,
277 | '',
278 | fileContent.trim(),
279 | '',
280 | '',
281 | defsTree.trim(),
282 | ''
283 | ].join('\n').trim();
284 |
285 | const aiOutput = await chat("s")(aiInput, { system: system_DepsPredictor, model: "s" });
286 | console.log("");
287 |
288 | const dependenciesMatch = aiOutput.match(/([\s\S]*)<\/DEPENDENCIES>/);
289 | if (!dependenciesMatch) {
290 | console.error("Error: AI output does not contain a valid DEPENDENCIES tag.");
291 | return [];
292 | }
293 |
294 | return dependenciesMatch[1].trim().split('\n').map(dep => dep.trim());
295 | }
296 |
297 | // Function to perform type checking based on file extension
298 | async function typeCheck(file) {
299 | let ext = path.extname(file);
300 | let cmd = `kind2 check ${file}`;
301 | try {
302 | var result = await execAsync(cmd);
303 | return result.stderr.trim() || result.stdout.trim();
304 | } catch (error) {
305 | return error.stderr.trim();
306 | }
307 | }
308 |
309 | // Main function to handle the refactoring process
310 | async function main() {
311 | // Check for correct usage and parse command-line arguments
312 | if (process.argv.length < 3) {
313 | console.log("Usage: kindcoder []");
314 | process.exit(1);
315 | }
316 |
317 | let file = process.argv[2];
318 | let request = process.argv[3];
319 | let model = process.argv[4] || "s";
320 |
321 | // Initialize the chat function with the specified model
322 | let ask = chat(model);
323 |
324 | // Get directory and file information
325 | let dir = path.dirname(file);
326 | let fileContent;
327 | try {
328 | fileContent = await fs.readFile(file, 'utf-8');
329 | } catch (e) {
330 | fileContent = "";
331 | }
332 | let dirContent = await fs.readdir(dir);
333 |
334 | // If the request is empty, replace it by a default request.
335 | if (request.trim() === '') {
336 | request = [
337 | "Update this file.",
338 | "- If it is empty, implement an initial template.",
339 | "- If it has holes, fill them, up to \"one layer\".",
340 | "- If it has no holes, fully complete it, as much as possible."
341 | ].join('\n');
342 | }
343 |
344 | // If the file is empty, ask the AI to fill with an initial template
345 | if (fileContent.trim() === '') {
346 | fileContent = [
347 | "This file is empty. Please replace it with a Kind2 definition. Example:",
348 | "",
349 | "```kind2",
350 | "/// Does foo.",
351 | "///",
352 | "/// # Input",
353 | "///",
354 | "/// * `x0` - Description",
355 | "/// * `x1` - Description",
356 | "/// ...",
357 | "///",
358 | "/// # Output",
359 | "///",
360 | "/// The result of doing foo",
361 | "",
362 | "use Lib/A/{a,b}",
363 | "use Lib/B/{c,d}",
364 | "...",
365 | "",
366 | "foo ...",
367 | "- x0: X0",
368 | "- x1: X1",
369 | "...",
370 | "",
371 | "body",
372 | "```",
373 | ].join('\n');
374 | }
375 |
376 | // Extract the definition name from the file path
377 | let defName = file.split('/book/')[1].replace('.kind2', '');
378 |
379 | // Collect direct and indirect dependencies
380 | let deps;
381 | try {
382 | let { stdout } = await execAsync(`kind2 deps ${defName}`);
383 | deps = stdout.trim().split('\n');
384 | } catch (e) {
385 | deps = [];
386 | }
387 |
388 | // Predict additional dependencies
389 | const predictedDeps = await predictDependencies(defName, fileContent);
390 | //console.log(JSON.stringify(predictedDeps,null,2));
391 | //process.exit();
392 | deps = [...new Set([...deps, ...predictedDeps])];
393 | deps = deps.filter(dep => dep !== defName);
394 |
395 | // Read dependent files
396 | let depFiles = await Promise.all(deps.map(async (dep) => {
397 | let depPath, content;
398 | let path0 = path.join(dir, '..', `${dep}.kind2`);
399 | let path1 = path.join(dir, '..', `${dep}/_.kind2`);
400 | try {
401 | content = await fs.readFile(path0, 'utf-8');
402 | depPath = path0;
403 | } catch (error) {
404 | try {
405 | content = await fs.readFile(path1, 'utf-8');
406 | depPath = path1;
407 | } catch (error) {
408 | return "";
409 | }
410 | }
411 | return `\n${content}\n`;
412 | }));
413 |
414 | // Perform initial type checking
415 | let initialCheck = (await typeCheck(defName)).replace(/\x1b\[[0-9;]*m/g, '');
416 |
417 | // Prepare AI input
418 | let aiInput = [
419 | ...depFiles,
420 | ``,
421 | fileContent,
422 | '',
423 | '',
424 | initialCheck,
425 | '',
426 | '',
427 | request,
428 | ''
429 | ].join('\n').trim();
430 |
431 | // Write a .prompt file with the system + aiInput strings
432 | await fs.writeFile('.kindcoder', system_KindCoder + '\n\n' + aiInput, 'utf-8');
433 |
434 | // Call the AI model
435 | let aiOutput = await ask(aiInput, { system: system_KindCoder, model });
436 | console.log("");
437 |
438 | // Extract the result from AI output
439 | let resultMatch = aiOutput.match(/([\s\S]*)<\/RESULT>/);
440 | if (!resultMatch) {
441 | console.error("Error: AI output does not contain a valid RESULT tag.");
442 | process.exit(1);
443 | }
444 |
445 | let result = resultMatch[1].trim();
446 |
447 | // Write the result back to the file
448 | await fs.writeFile(file, result, 'utf-8');
449 |
450 | console.log("File updated successfully.");
451 | }
452 |
453 | // Run the main function and handle any errors
454 | main().catch(console.error);
455 |
456 |
--------------------------------------------------------------------------------
/KIND2_GUIDE_AI.md:
--------------------------------------------------------------------------------
1 | # GUIDE FOR THE KIND2 LANGUAGE
2 |
3 | Kind2 is a minimal proof language based on the Calculus of Constructions. It is
4 | similar to Agda in capabilities, but has a raw syntax, and a much smaller core.
5 | Instead of a native datatype system, it uses λ-encodings to represent data. To
6 | make inductive proofs, it includes a lightweight primitive called Self Types.
7 |
8 | ## Kind2 Core Language
9 |
10 | All of Kind2 desugars to the following small core:
11 |
12 | ```
13 | Term ::=
14 | | all : ∀(x: A) B // the dependent function type (also called Pi Type)
15 | | lam : λx f // an inline function (also called lambda)
16 | | app : (f x y z) // function application (Lisp-style, MANDATORY parenthesis)
17 | | ann : {x :: T} // an inline annotation (type hint)
18 | | slf : $(x: A) T // self type, for λ-encoded inductive datatypes (see later)
19 | | ins : ~t // self inst, to consume a self type (see later)
20 | | ref : // top-level reference (expands to its definition)
21 | | let : let x = t // local definition, creates a new variable (runtime cloning)
22 | | use : use x = t // local definition, substitutes statically (no runtime cloning)
23 | | set : * // the only universe (kind has type-in-type)
24 | | num : // a numeric literal (48-bit unsigned integer)
25 | | op2 : (+ x y) // a numeric operation (Lisp-style, MANDATORY parenthesis)
26 | | swi : see below // a numeric pattern-match (with zero and succ cases)
27 | | u48 : U48 // a numeric type
28 | | hol : ?a // a typed hole, for debugging and context inspection
29 | | met : _ // an unification metavar (is solved by the checker)
30 | | var : // a variable
31 | ```
32 |
33 | ## Kind2 Syntax
34 |
35 | Since Kind2's core is so simple, it comes with many syntax sugars.
36 |
37 | ### Top-Level Function
38 |
39 | Every .kind2 file must define ONE top-level function:
40 |
41 | ```
42 | func ...
43 | - arg0: typ0
44 | - arg1: typ1
45 | - ...
46 | : ret_typ
47 |
48 | body
49 | ```
50 |
51 | Where:
52 | - p0, p1... are erased arguments
53 | - arg0, arg1... are the function arguments
54 | - ret_typ is the returned type
55 | - body is the function's body
56 |
57 | ### Top-Level Datatype
58 |
59 | Alternatively, a .kind2 tile can also define an inductive datatype:
60 |
61 | ```
62 | data Name ... (i0: I0) (i1: I1) ...
63 | | ctr0 (f0: F0) (f1: F1) ... : (Name p0 p1 ... i0 i1 ...)
64 | | ctr1 (f0: F0) (f1: F1) ... : (Name p0 p1 ... i0 i1 ...)
65 | | ...
66 | ```
67 |
68 | Where:
69 | - p0, p1... are parameters
70 | - i0, i1... are indices
71 | - ctr0, ctr1... are constructors
72 | - f0, f1... are fields
73 |
74 | Top-Level datatypes desugar to λ-encodings. The λ-encoded constructors must be
75 | created manually, in separate files. See examples below.
76 |
77 | ### Names, Paths and Use Aliases
78 |
79 | Kind2 doesn't need imports. Every file defines a single top-level definition,
80 | which can be addressed from any other file via its full path. Example:
81 |
82 | ```
83 | book/Nat/add/comm.kind2
84 | ```
85 |
86 | Defines:
87 |
88 | ```
89 | Nat/add/comm
90 | ```
91 |
92 | Which can be accessed directly from any other file, no 'import' needed.
93 |
94 | To shorten names, the 'use' declaration can be added to the beginning of a file:
95 |
96 | ```
97 | use Nat/{succ,zero}
98 | ```
99 |
100 | This locally expands 'succ' and 'zero' to 'Nat/succ' and 'Nat/zero'. It is
101 | specially useful to avoid typing full constructor names on 'match' cases.
102 |
103 | NOTE: when a definition is not found in `Foo/Bar.kind2`, Kind2 will try to
104 | look for it on `Foo/Bar/_.kind2`. The `_` is just a placeholder and is NOT
105 | part of the definition's name.
106 |
107 | ### Pattern-Matching
108 |
109 | To eliminate a datatype, the match syntax can be used:
110 |
111 | ```
112 | match x = expr with (a0: A0) (a1: A1) ... {
113 | Type/ctr0: ret0
114 | Type/ctr1: ret1
115 | ...
116 | }: motive
117 | ```
118 |
119 | Where:
120 | - x is the *name* of the scrutinee
121 | - expr is the *value* of scrutinee (optional)
122 | - a0, a1... are arguments to be *linearly passed down* to the branches (as an optimization, and helps proving)
123 | - ctr0, ctr1... are the matched cases
124 | - ret0, ret1... are the returned bodies of each case (with ctr fields available)
125 | - the motive is optional (useful for theorem proving)
126 |
127 | Kind will automatically make constructor fields available on their respective
128 | cases, named `.`. For example, on the `succ` case of
129 | Nat, if the scrutinee is called `num`, `num.pred` will be available.
130 |
131 | For this syntax to work, a top-level 'Type/match' definition must be provided.
132 |
133 | The 'match' keyword can be replaced by 'fold', to auto-recurse.
134 |
135 | This desugars to a self-inst and function applications.
136 |
137 | ### Numeric Pattern-Matching
138 |
139 | For matching on native U48 numbers, Kind2 provides a special syntax:
140 |
141 | ```
142 | switch x = expr {
143 | 0: zero_case
144 | _: succ_case
145 | }: motive
146 | ```
147 |
148 | ### Note on Parameters and Metavars
149 |
150 | Top-level definitions can have N parameters, or erased arguments. Example:
151 |
152 | ```
153 | // Pair/swap.kind2
154 | swap
155 | - pair: (Pair A B)
156 | ...
157 | ```
158 |
159 | There are two ways to call these functions.
160 |
161 | 1. Filling the parameters explicitly:
162 |
163 | ```
164 | (swap Nat (List Nat) (Pair/new Nat (List Nat) Nat/zero (List/nil Nat)))
165 | ```
166 |
167 | 2. Using metavars (`_`) to fill the parameters:
168 |
169 | ```
170 | (swap _ _ (Pair/new _ _ Nat/zero (List/nil _)))
171 | ```
172 |
173 | As you can see, using metavars is much more concise. As a rule of thumb, always
174 | use metavars on the function body, but write it fully on its arglist. Remember
175 | to always count the arguments: you need one metavar (`_`) per parameter (`<>`).
176 |
177 | ### Other Sugars
178 |
179 | - Lists: `[a, b, c]` (desugars to cons/nil)
180 | - Strings: `"foo"` (desugars to lists of u48 codepoints)
181 | - Equality: `{a = b}` (desugars to `(Equal _ a b)`)
182 | - Function: `A -> B` (desugars to `∀(x_: A) B`)
183 | - Comments: `// comment here`
184 |
185 | ## Kind2 Examples
186 |
187 | ### Nat/_.kind2
188 |
189 | ```
190 | /// Defines the natural numbers as an inductive datatype.
191 | ///
192 | /// # Constructors
193 | ///
194 | /// * `succ` - Represents the successor of a natural number (x+1).
195 | /// * `zero` - Represents the natural number zero (0).
196 |
197 | data Nat
198 | | succ (pred: Nat)
199 | | zero
200 | ```
201 |
202 | ### Nat/succ.kind2
203 |
204 | ```
205 | /// Constructs the successor of a natural number.
206 | ///
207 | /// # Input
208 | ///
209 | /// * `n` - The natural number to which we add 1.
210 | ///
211 | /// # Output
212 | ///
213 | /// The successor of `n`.
214 |
215 | succ
216 | - n: Nat
217 | : Nat
218 |
219 | ~λP λsucc λzero (succ n)
220 | ```
221 |
222 | ### Nat/zero.kind2
223 |
224 | ```
225 | /// Represents the zero natural number.
226 | ///
227 | /// # Output
228 | ///
229 | /// The zero natural number.
230 |
231 | zero
232 | : Nat
233 |
234 | ~λP λsucc λzero zero
235 | ```
236 |
237 | ### Nat/match.kind2
238 |
239 | ```
240 | /// Provides a way to pattern match on natural numbers.
241 | ///
242 | /// # Inputs
243 | ///
244 | /// * `P` - The motive of the elimination.
245 | /// * `s` - The successor case.
246 | /// * `z` - The zero case.
247 | /// * `n` - The natural number to match on.
248 | ///
249 | /// # Output
250 | ///
251 | /// The result of the elimination.
252 |
253 | match
254 | - P: Nat -> *
255 | - s: ∀(pred: Nat) (P (Nat/succ pred))
256 | - z: (P Nat/zero)
257 | - n: Nat
258 | : (P n)
259 |
260 | (~n P s z)
261 | ```
262 |
263 | ### Nat/add.kind2
264 |
265 | ```
266 | /// Adds two natural numbers.
267 | ///
268 | /// # Inputs
269 | ///
270 | /// * `a` - The first natural number.
271 | /// * `b` - The second natural number.
272 | ///
273 | /// # Output
274 | ///
275 | /// The sum of `a` and `b`.
276 |
277 | use Nat/{succ,zero}
278 |
279 | add
280 | - a: Nat
281 | - b: Nat
282 | : Nat
283 |
284 | match a {
285 | succ: (succ (add a.pred b))
286 | zero: b
287 | }
288 | ```
289 |
290 | ### Nat/equal.kind2
291 |
292 | ```
293 | /// Checks if two natural numbers are equal.
294 | ///
295 | /// # Inputs
296 | ///
297 | /// * `a` - The first natural number.
298 | /// * `b` - The second natural number.
299 | ///
300 | /// # Output
301 | ///
302 | /// `true` if `a` and `b` are equal, `false` otherwise.
303 |
304 | use Nat/{succ,zero}
305 | use Bool/{true,false}
306 |
307 | equal
308 | - a: Nat
309 | - b: Nat
310 | : Bool
311 |
312 | match a with (b: Nat) {
313 | succ: match b {
314 | succ: (equal a.pred b.pred)
315 | zero: false
316 | }
317 | zero: match b {
318 | succ: false
319 | zero: true
320 | }
321 | }
322 | ```
323 |
324 | ### List/_.kind2
325 |
326 | ```
327 | /// Defines a generic list datatype.
328 | ///
329 | /// # Parameters
330 | ///
331 | /// * `T` - The type of elements in the list.
332 | ///
333 | /// # Constructors
334 | ///
335 | /// * `cons` - Adds an element to the front of a list.
336 | /// * `nil` - Represents an empty list.
337 |
338 | data List
339 | | cons (head: T) (tail: (List T))
340 | | nil
341 | ```
342 |
343 | ### List/cons.kind2
344 |
345 | ```
346 | /// Constructs a new list by adding an element to the front of an existing list.
347 | ///
348 | /// # Parameters
349 | ///
350 | /// * `T` - The type of elements in the list.
351 | ///
352 | /// # Inputs
353 | ///
354 | /// * `head` - The element to add to the front of the list.
355 | /// * `tail` - The existing list.
356 | ///
357 | /// # Output
358 | ///
359 | /// A new list with `head` as its first element, followed by the elements of `tail`.
360 |
361 | cons
362 | - head: T
363 | - tail: (List T)
364 | : (List T)
365 |
366 | ~λP λcons λnil (cons head tail)
367 | ```
368 |
369 | ### List/nil.kind2
370 |
371 | ```
372 | /// Constructs an empty list.
373 | ///
374 | /// # Parameters
375 | ///
376 | /// * `T` - The type of elements in the list.
377 | ///
378 | /// # Output
379 | ///
380 | /// An empty list of type `(List T)`.
381 |
382 | nil
383 | : (List T)
384 |
385 | ~λP λcons λnil nil
386 | ```
387 |
388 | ### List/match.kind2
389 |
390 | ```
391 | /// Provides a way to pattern match on lists.
392 | ///
393 | /// # Parameters
394 | ///
395 | /// * `A` - The type of elements in the list.
396 | ///
397 | /// # Inputs
398 | ///
399 | /// * `P` - The motive of the elimination.
400 | /// * `c` - The cons case.
401 | /// * `n` - The nil case.
402 | /// * `xs` - The list to match on.
403 | ///
404 | /// # Output
405 | ///
406 | /// The result of the elimination.
407 |
408 | match
409 | - P: (List A) -> *
410 | - c: ∀(head: A) ∀(tail: (List A)) (P (List/cons A head tail))
411 | - n: (P (List/nil A))
412 | - xs: (List A)
413 | : (P xs)
414 |
415 | (~xs P c n)
416 | ```
417 |
418 | ### List/fold.kind2
419 |
420 | ```
421 | /// Folds a list from left to right.
422 | ///
423 | /// # Parameters
424 | ///
425 | /// * `A` - The type of elements in the list.
426 | ///
427 | /// # Inputs
428 | ///
429 | /// * `P` - The type of the accumulator and result.
430 | /// * `c` - The function to apply to each element and the accumulator.
431 | /// * `n` - The initial value of the accumulator.
432 | /// * `xs` - The list to fold.
433 | ///
434 | /// # Output
435 | ///
436 | /// The result of folding the list.
437 |
438 | use List/{cons,nil}
439 |
440 | List/fold (P: *)
441 | - c: A -> P -> P
442 | - n: P
443 | - xs: (List A)
444 | : P
445 |
446 | match xs {
447 | cons: (c xs.head (List/fold _ P c n xs.tail))
448 | nil: n
449 | }
450 | ```
451 |
452 | ### List/map.kind2
453 |
454 | ```
455 | /// Applies a function to each element of a list.
456 | ///
457 | /// # Parameters
458 | ///
459 | /// * `A` - The type of elements in the input list.
460 | /// * `B` - The type of elements in the output list.
461 | ///
462 | /// # Inputs
463 | ///
464 | /// * `xs` - The input list.
465 | /// * `fn` - The function to apply to each element.
466 | ///
467 | /// # Output
468 | ///
469 | /// A new list with the function applied to each element of the input list.
470 |
471 | use List/{cons,nil}
472 |
473 | map
474 | - xs: (List A)
475 | - fn: A -> B
476 | : (List B)
477 |
478 | match xs {
479 | cons:
480 | let head = (fn xs.head)
481 | let tail = (List/map _ _ xs.tail fn)
482 | (cons _ head tail)
483 | nil:
484 | (nil _)
485 | }
486 | ```
487 |
488 | ### List/filter.kind2
489 |
490 | ```
491 | /// Filters a list, keeping only elements that satisfy the given condition.
492 | ///
493 | /// # Parameters
494 | ///
495 | /// * `A` - The type of elements in the list.
496 | ///
497 | /// # Inputs
498 | ///
499 | /// * `cond` - A function that takes an element of type A and returns a Bool.
500 | /// * `list` - The input list of type (List A).
501 | ///
502 | /// # Output
503 | ///
504 | /// A new list containing only the elements for which `cond` returns `true`.
505 |
506 | use List/{cons,nil}
507 | use Bool/{true,false}
508 |
509 | filter
510 | - cond: A -> Bool
511 | - list: (List A)
512 | : (List A)
513 |
514 | match list {
515 | nil:
516 | (nil _)
517 | cons:
518 | let test = (cond list.head)
519 | let head = match test { true: (cons _ list.head) false: λx x }: (List A) -> (List A)
520 | let tail = (filter _ cond list.tail)
521 | (head tail)
522 | }
523 | ```
524 |
525 | ### STOP
526 |
527 | Take a moment to notice how EVERY constructor and function application in Kind2
528 | requires a parenthesis, just like Lisp. Trying to write `(foo x y)` without the
529 | surrounding parenthesis is a syntax error. Confirm this is true and move on.
530 |
531 | Also, note that ERASED arguments can usually be inferred, so, a metavar ('_')
532 | can be used. Example: '(func _ _ ... arg0 arg1 ...)' can be writen instead of
533 | '(func A B ... arg0 arg1 ...)'. Prefer metavars on the body of the function.
534 |
535 | ### Sigma/_.kind2
536 |
537 | ```
538 | /// Defines a dependent pair type, also known as a sigma type.
539 | ///
540 | /// # Parameters
541 | ///
542 | /// * `A` - The type of the first component.
543 | /// * `B` - A function type `A -> *` that determines the type of the second component based on the first.
544 | ///
545 | /// # Constructor
546 | ///
547 | /// * `new` - Creates a new sigma type instance.
548 |
549 | data Sigma *>
550 | | new (fst: A) (snd: (B fst))
551 | ```
552 |
553 | ### Equal/_.kind2
554 |
555 | ```
556 | /// Defines propositional equality between two values of the same type.
557 | ///
558 | /// # Parameters
559 | ///
560 | /// * `T` - The type of the values being compared.
561 | ///
562 | /// # Parameters
563 | ///
564 | /// * `a` - The first value.
565 | /// * `b` - The second value.
566 | ///
567 | /// # Constructor
568 | ///
569 | /// * `refl` - Represents reflexivity, i.e., that `a` equals itself.
570 |
571 | data Equal (a: T) (b: T)
572 | | refl (a: T) : (Equal T a a)
573 | ```
574 |
575 | ### Equal/refl.kind2
576 |
577 | ```
578 | /// Constructs a proof of reflexivity for propositional equality.
579 | ///
580 | /// # Parameters
581 | ///
582 | /// * `A` - The type of the value.
583 | ///
584 | /// # Input
585 | ///
586 | /// * `x` - The value for which to construct the reflexivity proof.
587 | ///
588 | /// # Output
589 | ///
590 | /// A proof that `x` is equal to itself.
591 |
592 | refl
593 | - x: A
594 | : (Equal A x x)
595 |
596 | ~ λP λrefl (refl x)
597 | ```
598 |
599 | ### Equal/apply.kind2
600 |
601 | ```
602 | /// Applies a function to both sides of an equality proof.
603 | ///
604 | /// # Parameters
605 | ///
606 | /// * `A` - The type of the compared values.
607 | /// * `B` - The type of the compared values after applying the function.
608 | /// * `a` - The first compared value.
609 | /// * `b` - The second compared value.
610 | ///
611 | /// # Inputs
612 | ///
613 | /// * `f` - The function to apply to both sides of the equality.
614 | /// * `e` - The proof of equality between `a` and `b`.
615 | ///
616 | /// # Output
617 | ///
618 | /// A proof that `(f a)` is equal to `(f b)`.
619 |
620 | use Equal/refl
621 |
622 | apply
623 | - f: A -> B
624 | - e: (Equal A a b)
625 | : (Equal B (f a) (f b))
626 |
627 | match e {
628 | refl: ~λPλe(e (f e.a))
629 | }: (Equal B (f e.a) (f e.b))
630 |
--------------------------------------------------------------------------------