lystn-cli 0.1.5 → 0.2.0

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/README.md CHANGED
@@ -1,35 +1,35 @@
1
- # lystn (npm package)
2
-
3
- Listen to AI coding assistants instead of reading them. This is the npm
4
- distribution of the [Lystn](https://github.com/burakayener/Lystn) CLI.
5
-
6
- ```bash
7
- npm install -g lystn-cli # or: pnpm add -g lystn-cli
8
- lystn config set server https://api.lystn.space
9
- lystn config set api_key <your key>
10
- lystn install # wires the Claude Code hook automatically
11
- lystn test # you should hear a voice
12
- ```
13
-
14
- ## How this package works
15
-
16
- The Lystn CLI is Python. This npm package is a thin launcher: on install
17
- (or on first run) it creates a private Python virtualenv inside the
18
- package directory and installs the matching `lystn` release from PyPI
19
- into it. Nothing touches your global Python or site-packages.
20
-
21
- - Requires **Python 3.10+** on the machine (macOS ships it; on Windows
22
- install from python.org with "Add to PATH").
23
- - **pnpm users:** pnpm v10+ skips install scripts by default — that's
24
- fine; the launcher sets itself up on the first `lystn` command instead.
25
- - Uninstall with `npm uninstall -g lystn-cli` (the venv lives inside the
26
- package dir and is removed with it). Run `lystn uninstall` first to
27
- remove the Claude Code hooks.
28
-
29
- ## Releasing (maintainers)
30
-
31
- 1. Publish the new version to PyPI first (see `docs/ops/INSTALL-BREW.md`
32
- section 1 — same artifact serves brew and npm).
33
- 2. Bump `version` in `packaging/npm/package.json` to the **same** version
34
- (the bootstrap pins `lystn==<version>` from PyPI).
35
- 3. `cd packaging/npm && npm publish`.
1
+ # lystn (npm package)
2
+
3
+ Listen to AI coding assistants instead of reading them. This is the npm
4
+ distribution of the [Lystn](https://github.com/burakayener/Lystn) CLI.
5
+
6
+ ```bash
7
+ npm install -g lystn-cli # or: pnpm add -g lystn-cli
8
+ lystn config set server https://api.lystn.space
9
+ lystn config set api_key <your key>
10
+ lystn install # wires the Claude Code hook automatically
11
+ lystn test # you should hear a voice
12
+ ```
13
+
14
+ ## How this package works
15
+
16
+ The Lystn CLI is Python. This npm package is a thin launcher: on install
17
+ (or on first run) it creates a private Python virtualenv inside the
18
+ package directory and installs the matching `lystn` release from PyPI
19
+ into it. Nothing touches your global Python or site-packages.
20
+
21
+ - Requires **Python 3.10+** on the machine (macOS ships it; on Windows
22
+ install from python.org with "Add to PATH").
23
+ - **pnpm users:** pnpm v10+ skips install scripts by default — that's
24
+ fine; the launcher sets itself up on the first `lystn` command instead.
25
+ - Uninstall with `npm uninstall -g lystn-cli` (the venv lives inside the
26
+ package dir and is removed with it). Run `lystn uninstall` first to
27
+ remove the Claude Code hooks.
28
+
29
+ ## Releasing (maintainers)
30
+
31
+ 1. Publish the new version to PyPI first (see `docs/ops/INSTALL-BREW.md`
32
+ section 1 — same artifact serves brew and npm).
33
+ 2. Bump `version` in `packaging/npm/package.json` to the **same** version
34
+ (the bootstrap pins `lystn==<version>` from PyPI).
35
+ 3. `cd packaging/npm && npm publish`.
package/bin/lystn.js CHANGED
@@ -1,28 +1,28 @@
1
- #!/usr/bin/env node
2
- /*
3
- * `lystn` launcher for the npm package.
4
- *
5
- * Delegates every invocation to the real Python CLI living in the
6
- * package-private venv (.venv/). If the venv is missing (e.g. pnpm
7
- * skipped postinstall), it bootstraps on the spot, then runs the command.
8
- */
9
- "use strict";
10
-
11
- const { spawnSync } = require("child_process");
12
- const fs = require("fs");
13
- const path = require("path");
14
-
15
- const { bootstrap, venvLystn } = require(path.join(
16
- __dirname,
17
- "..",
18
- "scripts",
19
- "bootstrap.js"
20
- ));
21
-
22
- const exe = venvLystn();
23
- if (!fs.existsSync(exe) && !bootstrap()) {
24
- process.exit(1);
25
- }
26
-
27
- const res = spawnSync(exe, process.argv.slice(2), { stdio: "inherit" });
28
- process.exit(res.status === null ? 1 : res.status);
1
+ #!/usr/bin/env node
2
+ /*
3
+ * `lystn` launcher for the npm package.
4
+ *
5
+ * Delegates every invocation to the real Python CLI living in the
6
+ * package-private venv (.venv/). If the venv is missing (e.g. pnpm
7
+ * skipped postinstall), it bootstraps on the spot, then runs the command.
8
+ */
9
+ "use strict";
10
+
11
+ const { spawnSync } = require("child_process");
12
+ const fs = require("fs");
13
+ const path = require("path");
14
+
15
+ const { bootstrap, venvLystn } = require(path.join(
16
+ __dirname,
17
+ "..",
18
+ "scripts",
19
+ "bootstrap.js"
20
+ ));
21
+
22
+ const exe = venvLystn();
23
+ if (!fs.existsSync(exe) && !bootstrap()) {
24
+ process.exit(1);
25
+ }
26
+
27
+ const res = spawnSync(exe, process.argv.slice(2), { stdio: "inherit" });
28
+ process.exit(res.status === null ? 1 : res.status);
package/package.json CHANGED
@@ -1,30 +1,30 @@
1
- {
2
- "name": "lystn-cli",
3
- "version": "0.1.5",
4
- "description": "Listen to AI coding assistant responses through your speakers.",
5
- "homepage": "https://github.com/burakayener/Lystn",
6
- "license": "SEE LICENSE IN LICENSE",
7
- "bin": {
8
- "lystn": "bin/lystn.js"
9
- },
10
- "scripts": {
11
- "postinstall": "node scripts/bootstrap.js || exit 0"
12
- },
13
- "files": [
14
- "bin/",
15
- "scripts/",
16
- "README.md"
17
- ],
18
- "engines": {
19
- "node": ">=18"
20
- },
21
- "keywords": [
22
- "tts",
23
- "claude-code",
24
- "codex",
25
- "ai-agents",
26
- "voice",
27
- "accessibility",
28
- "adhd"
29
- ]
30
- }
1
+ {
2
+ "name": "lystn-cli",
3
+ "version": "0.2.0",
4
+ "description": "Listen to AI coding assistant responses through your speakers.",
5
+ "homepage": "https://github.com/burakayener/Lystn",
6
+ "license": "SEE LICENSE IN LICENSE",
7
+ "bin": {
8
+ "lystn": "bin/lystn.js"
9
+ },
10
+ "scripts": {
11
+ "postinstall": "node scripts/bootstrap.js || exit 0"
12
+ },
13
+ "files": [
14
+ "bin/",
15
+ "scripts/",
16
+ "README.md"
17
+ ],
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "keywords": [
22
+ "tts",
23
+ "claude-code",
24
+ "codex",
25
+ "ai-agents",
26
+ "voice",
27
+ "accessibility",
28
+ "adhd"
29
+ ]
30
+ }
@@ -1,129 +1,129 @@
1
- #!/usr/bin/env node
2
- /*
3
- * Bootstrap the private Python environment for the lystn npm package.
4
- *
5
- * Runs at `npm install -g lystn-cli` (postinstall) AND lazily from bin/lystn.js
6
- * if the venv is missing — pnpm v10+ skips postinstall scripts by default,
7
- * so the launcher must be able to self-bootstrap on first run.
8
- *
9
- * What it does:
10
- * 1. Find a Python >= 3.10 (python3.13..3.10, python3, python, py -3).
11
- * 2. Create a venv inside the package directory (.venv/).
12
- * 3. pip install lystn==<version from package.json> into it.
13
- */
14
- "use strict";
15
-
16
- const { spawnSync } = require("child_process");
17
- const fs = require("fs");
18
- const path = require("path");
19
-
20
- const PKG_ROOT = path.join(__dirname, "..");
21
- const VENV_DIR = path.join(PKG_ROOT, ".venv");
22
- const IS_WIN = process.platform === "win32";
23
- const PYPI_VERSION = require(path.join(PKG_ROOT, "package.json")).version;
24
-
25
- function run(cmd, args, opts) {
26
- return spawnSync(cmd, args, { encoding: "utf-8", ...opts });
27
- }
28
-
29
- function pythonVersionOk(exe, viaLauncherArgs) {
30
- const args = (viaLauncherArgs || []).concat([
31
- "-c",
32
- "import sys; print('%d.%d' % sys.version_info[:2])",
33
- ]);
34
- const res = run(exe, args);
35
- if (res.status !== 0 || !res.stdout) return false;
36
- const [maj, min] = res.stdout.trim().split(".").map(Number);
37
- return maj === 3 && min >= 10;
38
- }
39
-
40
- function findPython() {
41
- const candidates = IS_WIN
42
- ? [
43
- ["py", ["-3"]],
44
- ["python", []],
45
- ["python3", []],
46
- ]
47
- : [
48
- ["python3.13", []],
49
- ["python3.12", []],
50
- ["python3.11", []],
51
- ["python3.10", []],
52
- ["python3", []],
53
- ["python", []],
54
- ];
55
- for (const [exe, extraArgs] of candidates) {
56
- if (pythonVersionOk(exe, extraArgs)) return { exe, extraArgs };
57
- }
58
- return null;
59
- }
60
-
61
- function venvPython() {
62
- return IS_WIN
63
- ? path.join(VENV_DIR, "Scripts", "python.exe")
64
- : path.join(VENV_DIR, "bin", "python");
65
- }
66
-
67
- function venvLystn() {
68
- return IS_WIN
69
- ? path.join(VENV_DIR, "Scripts", "lystn.exe")
70
- : path.join(VENV_DIR, "bin", "lystn");
71
- }
72
-
73
- function bootstrap() {
74
- if (fs.existsSync(venvLystn())) return true; // already done
75
-
76
- const py = findPython();
77
- if (!py) {
78
- console.error(
79
- "[lystn] Python 3.10+ was not found on this system.\n" +
80
- "[lystn] The lystn CLI runs on Python. Install it from:\n" +
81
- "[lystn] macOS: brew install python (or use: brew install burakayener/lystn/lystn)\n" +
82
- "[lystn] Windows: https://www.python.org/downloads/ (check 'Add to PATH')\n" +
83
- "[lystn] Linux: your package manager, e.g. apt install python3\n" +
84
- "[lystn] Then run any `lystn` command again — setup resumes automatically."
85
- );
86
- return false;
87
- }
88
-
89
- console.error("[lystn] First-run setup: preparing the speech engine ...");
90
- let res = run(py.exe, py.extraArgs.concat(["-m", "venv", VENV_DIR]), {
91
- stdio: ["ignore", "inherit", "inherit"],
92
- });
93
- if (res.status !== 0) {
94
- console.error("[lystn] Failed to create a Python environment.");
95
- return false;
96
- }
97
-
98
- res = run(
99
- venvPython(),
100
- ["-m", "pip", "install", "--quiet", `lystn==${PYPI_VERSION}`],
101
- { stdio: ["ignore", "inherit", "inherit"] }
102
- );
103
- if (res.status !== 0 || !fs.existsSync(venvLystn())) {
104
- console.error(
105
- "[lystn] Failed to install the lystn engine from PyPI.\n" +
106
- "[lystn] Check your network and try again with any `lystn` command."
107
- );
108
- // Leave no half-built venv behind so the next run retries cleanly.
109
- try {
110
- fs.rmSync(VENV_DIR, { recursive: true, force: true });
111
- } catch (_) {}
112
- return false;
113
- }
114
-
115
- console.error("[lystn] Ready. Try: lystn config set api_key <key> && lystn test");
116
- return true;
117
- }
118
-
119
- module.exports = { bootstrap, venvLystn };
120
-
121
- if (require.main === module) {
122
- // Postinstall context: never fail the npm install — the launcher
123
- // self-bootstraps later if this didn't complete.
124
- try {
125
- bootstrap();
126
- } catch (err) {
127
- console.error(`[lystn] Setup deferred to first run (${err.message}).`);
128
- }
129
- }
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Bootstrap the private Python environment for the lystn npm package.
4
+ *
5
+ * Runs at `npm install -g lystn-cli` (postinstall) AND lazily from bin/lystn.js
6
+ * if the venv is missing — pnpm v10+ skips postinstall scripts by default,
7
+ * so the launcher must be able to self-bootstrap on first run.
8
+ *
9
+ * What it does:
10
+ * 1. Find a Python >= 3.10 (python3.13..3.10, python3, python, py -3).
11
+ * 2. Create a venv inside the package directory (.venv/).
12
+ * 3. pip install lystn==<version from package.json> into it.
13
+ */
14
+ "use strict";
15
+
16
+ const { spawnSync } = require("child_process");
17
+ const fs = require("fs");
18
+ const path = require("path");
19
+
20
+ const PKG_ROOT = path.join(__dirname, "..");
21
+ const VENV_DIR = path.join(PKG_ROOT, ".venv");
22
+ const IS_WIN = process.platform === "win32";
23
+ const PYPI_VERSION = require(path.join(PKG_ROOT, "package.json")).version;
24
+
25
+ function run(cmd, args, opts) {
26
+ return spawnSync(cmd, args, { encoding: "utf-8", ...opts });
27
+ }
28
+
29
+ function pythonVersionOk(exe, viaLauncherArgs) {
30
+ const args = (viaLauncherArgs || []).concat([
31
+ "-c",
32
+ "import sys; print('%d.%d' % sys.version_info[:2])",
33
+ ]);
34
+ const res = run(exe, args);
35
+ if (res.status !== 0 || !res.stdout) return false;
36
+ const [maj, min] = res.stdout.trim().split(".").map(Number);
37
+ return maj === 3 && min >= 10;
38
+ }
39
+
40
+ function findPython() {
41
+ const candidates = IS_WIN
42
+ ? [
43
+ ["py", ["-3"]],
44
+ ["python", []],
45
+ ["python3", []],
46
+ ]
47
+ : [
48
+ ["python3.13", []],
49
+ ["python3.12", []],
50
+ ["python3.11", []],
51
+ ["python3.10", []],
52
+ ["python3", []],
53
+ ["python", []],
54
+ ];
55
+ for (const [exe, extraArgs] of candidates) {
56
+ if (pythonVersionOk(exe, extraArgs)) return { exe, extraArgs };
57
+ }
58
+ return null;
59
+ }
60
+
61
+ function venvPython() {
62
+ return IS_WIN
63
+ ? path.join(VENV_DIR, "Scripts", "python.exe")
64
+ : path.join(VENV_DIR, "bin", "python");
65
+ }
66
+
67
+ function venvLystn() {
68
+ return IS_WIN
69
+ ? path.join(VENV_DIR, "Scripts", "lystn.exe")
70
+ : path.join(VENV_DIR, "bin", "lystn");
71
+ }
72
+
73
+ function bootstrap() {
74
+ if (fs.existsSync(venvLystn())) return true; // already done
75
+
76
+ const py = findPython();
77
+ if (!py) {
78
+ console.error(
79
+ "[lystn] Python 3.10+ was not found on this system.\n" +
80
+ "[lystn] The lystn CLI runs on Python. Install it from:\n" +
81
+ "[lystn] macOS: brew install python (or use: brew install burakayener/lystn/lystn)\n" +
82
+ "[lystn] Windows: https://www.python.org/downloads/ (check 'Add to PATH')\n" +
83
+ "[lystn] Linux: your package manager, e.g. apt install python3\n" +
84
+ "[lystn] Then run any `lystn` command again — setup resumes automatically."
85
+ );
86
+ return false;
87
+ }
88
+
89
+ console.error("[lystn] First-run setup: preparing the speech engine ...");
90
+ let res = run(py.exe, py.extraArgs.concat(["-m", "venv", VENV_DIR]), {
91
+ stdio: ["ignore", "inherit", "inherit"],
92
+ });
93
+ if (res.status !== 0) {
94
+ console.error("[lystn] Failed to create a Python environment.");
95
+ return false;
96
+ }
97
+
98
+ res = run(
99
+ venvPython(),
100
+ ["-m", "pip", "install", "--quiet", `lystn==${PYPI_VERSION}`],
101
+ { stdio: ["ignore", "inherit", "inherit"] }
102
+ );
103
+ if (res.status !== 0 || !fs.existsSync(venvLystn())) {
104
+ console.error(
105
+ "[lystn] Failed to install the lystn engine from PyPI.\n" +
106
+ "[lystn] Check your network and try again with any `lystn` command."
107
+ );
108
+ // Leave no half-built venv behind so the next run retries cleanly.
109
+ try {
110
+ fs.rmSync(VENV_DIR, { recursive: true, force: true });
111
+ } catch (_) {}
112
+ return false;
113
+ }
114
+
115
+ console.error("[lystn] Ready. Try: lystn config set api_key <key> && lystn test");
116
+ return true;
117
+ }
118
+
119
+ module.exports = { bootstrap, venvLystn };
120
+
121
+ if (require.main === module) {
122
+ // Postinstall context: never fail the npm install — the launcher
123
+ // self-bootstraps later if this didn't complete.
124
+ try {
125
+ bootstrap();
126
+ } catch (err) {
127
+ console.error(`[lystn] Setup deferred to first run (${err.message}).`);
128
+ }
129
+ }