sessioncost 0.0.0-stage → 0.5.15

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kish21
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,17 @@
1
- # Temporary Holding Version
1
+ # SessionCost
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ See how many tokens each turn of your coding-agent chat sends, why, and what to cut. Claude Code, Codex, Cursor,
4
+ Antigravity and GitHub Copilot. Local: it reads log files on your computer and sends nothing anywhere.
5
+
6
+ ```
7
+ npx sessioncost # the report of the newest chat in this folder
8
+ npx sessioncost list # your recent chats here
9
+ npx sessioncost setup # add /sessioncost to your agents
10
+ ```
11
+
12
+ SessionCost is a Python tool. The first run installs it once into a private environment in
13
+ `~/.sessioncost/npm/venv` (with [uv](https://docs.astral.sh/uv/) when you have it, which also fetches Python for you;
14
+ otherwise with your Python 3.10 or newer). Your own Python is left untouched.
15
+
16
+ Every command: [kish21.github.io/sessioncost-help](https://kish21.github.io/sessioncost-help/reference/commands/).
17
+ Source and the other ways to install: [github.com/kish21/sessioncost](https://github.com/kish21/sessioncost).
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+ // `npx sessioncost …`: runs SessionCost, a Python tool, for people who start things with npm.
3
+ //
4
+ // The first run makes a private Python environment in ~/.sessioncost/npm/venv and installs this exact version of
5
+ // SessionCost there (from PyPI; from the folder around this one when it sits in a SessionCost source tree, as in a
6
+ // test build). Later runs only start it. With uv on the PATH, uv makes the environment and fetches Python itself
7
+ // when none is installed; without it, a Python 3.10 or newer on the PATH is used. The environment stays: `setup` and
8
+ // the guard write the path of the Python they run with into each agent's settings.
9
+ // Nothing here reads or sends your sessions; the install only fetches SessionCost itself.
10
+ "use strict";
11
+
12
+ const fs = require("fs");
13
+ const os = require("os");
14
+ const path = require("path");
15
+ const { spawnSync } = require("child_process");
16
+
17
+ const VERSION = require("../package.json").version;
18
+ const WINDOWS = process.platform === "win32";
19
+ const PROBE_MS = 20000; // starting a Python (Windows Store Python takes ~2 s)
20
+ const INSTALL_MS = 600000; // making the environment and installing
21
+
22
+ function home(env = process.env) {
23
+ return env.SESSIONCOST_NPM_HOME || path.join(os.homedir(), ".sessioncost", "npm");
24
+ }
25
+
26
+ function venvPython(venv) {
27
+ return WINDOWS ? path.join(venv, "Scripts", "python.exe") : path.join(venv, "bin", "python");
28
+ }
29
+
30
+ // What to install: this version from PyPI, or the source tree this package sits in (a test build), or what
31
+ // SESSIONCOST_PIP_SPEC says.
32
+ function spec(env = process.env, here = __dirname) {
33
+ if (env.SESSIONCOST_PIP_SPEC) return env.SESSIONCOST_PIP_SPEC;
34
+ const tree = path.resolve(here, "..", "..");
35
+ try {
36
+ if (/^name = "sessioncost"$/m.test(fs.readFileSync(path.join(tree, "pyproject.toml"), "utf8"))) return tree;
37
+ } catch (e) { /* not in a source tree */ }
38
+ return `sessioncost==${VERSION}`;
39
+ }
40
+
41
+ function run(cmd, args, timeout, quiet = true) {
42
+ const r = spawnSync(cmd, args, { stdio: quiet ? "pipe" : "inherit", timeout, encoding: "utf8", windowsHide: true });
43
+ return { ok: !r.error && r.status === 0, out: (r.stdout || "").trim(), err: r.error ? String(r.error.message) : (r.stderr || "").trim() };
44
+ }
45
+
46
+ function onPath(name, env = process.env) {
47
+ const exts = WINDOWS ? [".exe", ".cmd", ".bat", ""] : [""];
48
+ for (const dir of (env.PATH || env.Path || "").split(path.delimiter)) {
49
+ for (const ext of exts) {
50
+ const f = path.join(dir, name + ext);
51
+ if (!dir) continue;
52
+ try { if (fs.statSync(f).isFile()) return f; } catch (e) {
53
+ // Windows Store Python's python.exe is an app link that cannot be opened for stat: accept the link itself
54
+ // (whether it runs is checked next)
55
+ try { if (fs.lstatSync(f).isSymbolicLink()) return f; } catch (e2) { /* next */ }
56
+ }
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+
62
+ // A Python 3.10 or newer on the PATH: [command, ...its first arguments], or null.
63
+ function findPython(which = onPath, runner = run) {
64
+ const tries = WINDOWS ? [["py", "-3"], ["python"], ["python3"]] : [["python3"], ["python"]];
65
+ for (const [name, ...first] of tries) {
66
+ const exe = which(name);
67
+ if (!exe) continue;
68
+ const r = runner(exe, [...first, "-c", "import sys; print(int(sys.version_info >= (3, 10)))"], PROBE_MS);
69
+ if (r.ok && r.out === "1") return [exe, ...first];
70
+ }
71
+ return null;
72
+ }
73
+
74
+ // The steps that make the environment and install SessionCost into it: [[command, args], ...], or null.
75
+ function installSteps(venv, what, which = onPath, runner = run) {
76
+ const py = venvPython(venv);
77
+ const uv = which("uv");
78
+ if (uv) {
79
+ return [[uv, ["venv", "--quiet", "--python", ">=3.10", venv]],
80
+ [uv, ["pip", "install", "--quiet", "--python", py, "--reinstall-package", "sessioncost", what]]];
81
+ }
82
+ const python = findPython(which, runner);
83
+ if (!python) return null;
84
+ return [[python[0], [...python.slice(1), "-m", "venv", venv]],
85
+ [py, ["-m", "pip", "install", "--quiet", "--disable-pip-version-check", "--force-reinstall", "--no-deps", what]]];
86
+ }
87
+
88
+ function main(argv = process.argv.slice(2), env = process.env) {
89
+ const dir = home(env);
90
+ const venv = path.join(dir, "venv");
91
+ const stamp = path.join(dir, "installed.txt"); // what is in the environment: changes when this package does
92
+ const what = spec(env);
93
+ const want = `${VERSION} ${what}`;
94
+ const py = venvPython(venv);
95
+ let have = "";
96
+ try { have = fs.readFileSync(stamp, "utf8").trim(); } catch (e) { /* first run */ }
97
+ if (have !== want || !fs.existsSync(py)) {
98
+ const steps = installSteps(venv, what);
99
+ if (!steps) {
100
+ console.error("SessionCost runs on Python. Install uv (https://docs.astral.sh/uv/getting-started/installation/), " +
101
+ "which fetches Python by itself, or Python 3.10 or newer (https://www.python.org/downloads/), " +
102
+ "then run npx sessioncost again.");
103
+ return 1;
104
+ }
105
+ console.error(`SessionCost ${VERSION}: installing once into ${venv} …`);
106
+ fs.rmSync(venv, { recursive: true, force: true });
107
+ fs.mkdirSync(dir, { recursive: true });
108
+ fs.rmSync(stamp, { force: true });
109
+ for (const [cmd, args] of steps) {
110
+ const r = run(cmd, args, INSTALL_MS);
111
+ if (!r.ok) {
112
+ console.error(`SessionCost could not be installed: ${path.basename(cmd)} ${args.join(" ")}\n${r.err}`.trim());
113
+ console.error("Check your internet connection, then run npx sessioncost again.");
114
+ fs.rmSync(venv, { recursive: true, force: true });
115
+ return 1;
116
+ }
117
+ }
118
+ fs.writeFileSync(stamp, want + "\n");
119
+ }
120
+ const r = spawnSync(py, ["-m", "sessioncost", ...argv], { stdio: "inherit" });
121
+ if (r.error) {
122
+ console.error(`SessionCost could not start: ${r.error.message}`);
123
+ return 1;
124
+ }
125
+ return r.status === null ? 1 : r.status;
126
+ }
127
+
128
+ module.exports = { spec, findPython, installSteps, venvPython, home, main, VERSION };
129
+
130
+ if (require.main === module) process.exitCode = main();
package/package.json CHANGED
@@ -1,6 +1,13 @@
1
1
  {
2
2
  "name": "sessioncost",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.5.15",
4
+ "description": "See how many tokens each turn of your coding-agent chat sends, why, and what to cut. Runs the SessionCost Python tool (installs it once, privately).",
5
+ "license": "MIT",
6
+ "author": "kish21",
7
+ "homepage": "https://github.com/kish21/sessioncost",
8
+ "repository": { "type": "git", "url": "git+https://github.com/kish21/sessioncost.git" },
9
+ "keywords": ["claude-code", "codex", "cursor", "antigravity", "copilot", "tokens", "cost"],
10
+ "bin": { "sessioncost": "bin/sessioncost.js" },
11
+ "files": ["bin/", "README.md", "LICENSE"],
12
+ "engines": { "node": ">=18" }
13
+ }