Skip to content

Commit c3392f9

Browse files
committed
Add token counter
1 parent 0ca6c8f commit c3392f9

File tree

7 files changed

+74
-2
lines changed

7 files changed

+74
-2
lines changed

bun.lockb

351 Bytes
Binary file not shown.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"ora": "^7.0.1",
3636
"pretty-bytes": "^6.1.1",
3737
"rxjs": "^7.8.1",
38+
"tiktoken": "^1.0.15",
3839
"wretch": "^2.8.1",
3940
"xml2js": "^0.6.2",
4041
"zod": "^3.22.4",

src/cli/assistant/assistant.utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export const promptAssistantConfig = async (
7878

7979
const shouldEditInstructions = await confirm({
8080
message: "Edit instructions?",
81-
default: false,
81+
default: true,
8282
});
8383

8484
let instructions = assistant?.instructions ?? "";

src/cli/entry.cli.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ import { listAssistantsAction } from "./assistant/list-assistants.cli.js";
99
import { createAssistantAction } from "./assistant/create-assistant.cli.js";
1010
import { updateAssistantAction } from "./assistant/update-assistant.cli.js";
1111
import { deleteAssistantAction } from "./assistant/delete-assistant.cli.js";
12+
import { countTokensAction } from "./helpers/count-tokens.cli.js";
1213

1314
const program = new Command();
1415
program
1516
.name("ai")
1617
.description("Use your OpenAI assistant from the command line")
1718
.version("2.0.0");
1819

19-
const runCommand = program
20+
program
2021
.command("chat", { isDefault: true, hidden: true })
2122
.argument("[args...]")
2223
.description("Start a chat with an assistant")
@@ -29,6 +30,18 @@ const runCommand = program
2930
await appAction();
3031
});
3132

33+
const utilsCommand = program
34+
.command("utils")
35+
.alias("u")
36+
.description("Useful utilities");
37+
38+
utilsCommand
39+
.command("count-tokens")
40+
.alias("ct")
41+
.description("Count tokens in a project")
42+
.argument("[globs]", "Globs to count tokens from")
43+
.action(countTokensAction);
44+
3245
const assistantsCommand = program
3346
.command("assistants")
3447
.alias("ass")

src/cli/helpers/count-tokens.cli.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import ora from "ora";
2+
import { countGlobToken } from "../../utils/tokens.utils.js";
3+
4+
export const countTokensAction = async (globFilters?: string) => {
5+
const spinner = ora({
6+
text: "Counting tokens",
7+
color: "blue",
8+
}).start();
9+
10+
const res = await countGlobToken(globFilters);
11+
12+
spinner.stopAndPersist({
13+
text: `Found ${res.tokensCount} tokens accross ${res.filesCount} files (OpenAI tokens)`,
14+
});
15+
};

src/utils/fs.utils.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { promises as fs } from "fs";
2+
import { join } from "path";
3+
4+
export function readFile(path: string, cwd?: string) {
5+
const fullPath = cwd ? join(cwd, path) : path;
6+
return fs.readFile(fullPath, "utf-8").catch((e) => {
7+
if (e.code === "ENOENT") throw new Error("File does not exist");
8+
throw e;
9+
});
10+
}

src/utils/tokens.utils.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { get_encoding } from "tiktoken";
2+
import { readFile } from "./fs.utils.js";
3+
import { glob } from "zx";
4+
5+
export function countTokens(text: string): number {
6+
const encoding = get_encoding("cl100k_base");
7+
const length = encoding.encode(text).length;
8+
encoding.free();
9+
return length;
10+
}
11+
12+
export async function countFileToken(path: string, cwd?: string) {
13+
const content = await readFile(path, cwd);
14+
return countTokens(content);
15+
}
16+
17+
export async function countGlobToken(globsString: string = "**/*") {
18+
const globs = globsString.split(",");
19+
const includes = globs.filter((g) => !g.startsWith("!"));
20+
const excludes = globs.filter((g) => g.startsWith("!"));
21+
22+
const paths = await glob(includes, {
23+
ignore: excludes,
24+
});
25+
26+
if (!paths) throw new Error("No files found");
27+
28+
const sizes = await Promise.all(paths.map((path) => countFileToken(path)));
29+
return {
30+
filesCount: paths.length,
31+
tokensCount: sizes.reduce((acc, size) => acc + size, 0),
32+
};
33+
}

0 commit comments

Comments
 (0)