taken-gh 0.7.5

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 RogueAlg0
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 ADDED
@@ -0,0 +1,25 @@
1
+ [![npm](https://img.shields.io/npm/v/taken-gh?cacheSeconds=3600)](https://www.npmjs.com/package/taken-gh)
2
+
3
+ # taken-gh
4
+
5
+ The npm installer for [taken](https://github.com/RogueAlg0/taken), the small CLI that answers "taken?" for any GitHub issue before you volunteer for it.
6
+
7
+ ## What this installs
8
+
9
+ Installing this package runs a postinstall step that makes sure the real `taken` Python CLI is on your PATH:
10
+
11
+ - If `taken --version` already reports this package's version, nothing happens.
12
+ - Otherwise it runs `python3 -m pip install --user "taken-gh==<this version>"`, retrying once with `--break-system-packages` on systems where pip refuses user installs (Debian 12+, Ubuntu 23.04+).
13
+
14
+ The postinstall never fails your npm install: if Python or pip is missing, or the pip install fails, it prints the manual install command and moves on.
15
+
16
+ ## Binaries
17
+
18
+ - `taken` — check whether a GitHub issue is already taken: `taken check owner/repo#123`
19
+ - `taken-mcp` — the MCP server exposing the same checks to AI assistants
20
+
21
+ Both are thin shims: they find the real Python-installed binary on your PATH and forward arguments, stdin/stdout/stderr, signals, and the exit code.
22
+
23
+ ## Versioning
24
+
25
+ This package's version always matches the `taken-gh` PyPI release of the same number. The postinstall installs exactly that version, so the two can never drift.
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ // npm shim for the real `taken` CLI (installed by the postinstall step via pip).
4
+ // Finds the binary of the same name on PATH and forwards everything to it:
5
+ // arguments, stdin/stdout/stderr, signals, and the exit code.
6
+ const { spawn } = require("node:child_process");
7
+ const fs = require("node:fs");
8
+ const path = require("node:path");
9
+
10
+ const NAME = path.basename(__filename, ".js");
11
+ // Real path of this shim, so findOnPath can skip it: when npm links the shim
12
+ // itself onto PATH as `taken`, a naive lookup would find the shim first and
13
+ // recurse forever.
14
+ const SELF_REALPATH = fs.realpathSync(__filename);
15
+
16
+ function findOnPath(name) {
17
+ const pathEnv = process.env.PATH || "";
18
+ for (const dir of pathEnv.split(path.delimiter)) {
19
+ if (!dir) continue;
20
+ const candidate = path.join(dir, name);
21
+ try {
22
+ fs.accessSync(candidate, fs.constants.X_OK);
23
+ if (!fs.statSync(candidate).isFile()) continue;
24
+ // Skip this very shim if npm put it on PATH ahead of the real binary.
25
+ if (fs.realpathSync(candidate) === SELF_REALPATH) continue;
26
+ return candidate;
27
+ } catch {
28
+ // not in this directory, keep looking
29
+ }
30
+ }
31
+ return null;
32
+ }
33
+
34
+ const target = findOnPath(NAME);
35
+ if (!target) {
36
+ console.error(
37
+ `${NAME} is not installed.\n` +
38
+ "The npm postinstall step normally installs it with pip. " +
39
+ "To install it by hand, run:\n" +
40
+ " python3 -m pip install --user taken-gh"
41
+ );
42
+ process.exit(1);
43
+ }
44
+
45
+ const child = spawn(target, process.argv.slice(2), { stdio: "inherit" });
46
+
47
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
48
+ process.on(sig, () => child.kill(sig));
49
+ }
50
+
51
+ child.on("error", (err) => {
52
+ console.error(`Could not start ${NAME}: ${err.message}`);
53
+ process.exit(1);
54
+ });
55
+
56
+ child.on("exit", (code, signal) => {
57
+ if (signal) {
58
+ // Re-raise so the caller's exit status reflects the signal.
59
+ process.kill(process.pid, signal);
60
+ } else {
61
+ process.exit(code === null ? 1 : code);
62
+ }
63
+ });
package/bin/taken.js ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ // npm shim for the real `taken` CLI (installed by the postinstall step via pip).
4
+ // Finds the binary of the same name on PATH and forwards everything to it:
5
+ // arguments, stdin/stdout/stderr, signals, and the exit code.
6
+ const { spawn } = require("node:child_process");
7
+ const fs = require("node:fs");
8
+ const path = require("node:path");
9
+
10
+ const NAME = path.basename(__filename, ".js");
11
+ // Real path of this shim, so findOnPath can skip it: when npm links the shim
12
+ // itself onto PATH as `taken`, a naive lookup would find the shim first and
13
+ // recurse forever.
14
+ const SELF_REALPATH = fs.realpathSync(__filename);
15
+
16
+ function findOnPath(name) {
17
+ const pathEnv = process.env.PATH || "";
18
+ for (const dir of pathEnv.split(path.delimiter)) {
19
+ if (!dir) continue;
20
+ const candidate = path.join(dir, name);
21
+ try {
22
+ fs.accessSync(candidate, fs.constants.X_OK);
23
+ if (!fs.statSync(candidate).isFile()) continue;
24
+ // Skip this very shim if npm put it on PATH ahead of the real binary.
25
+ if (fs.realpathSync(candidate) === SELF_REALPATH) continue;
26
+ return candidate;
27
+ } catch {
28
+ // not in this directory, keep looking
29
+ }
30
+ }
31
+ return null;
32
+ }
33
+
34
+ const target = findOnPath(NAME);
35
+ if (!target) {
36
+ console.error(
37
+ `${NAME} is not installed.\n` +
38
+ "The npm postinstall step normally installs it with pip. " +
39
+ "To install it by hand, run:\n" +
40
+ " python3 -m pip install --user taken-gh"
41
+ );
42
+ process.exit(1);
43
+ }
44
+
45
+ const child = spawn(target, process.argv.slice(2), { stdio: "inherit" });
46
+
47
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP"]) {
48
+ process.on(sig, () => child.kill(sig));
49
+ }
50
+
51
+ child.on("error", (err) => {
52
+ console.error(`Could not start ${NAME}: ${err.message}`);
53
+ process.exit(1);
54
+ });
55
+
56
+ child.on("exit", (code, signal) => {
57
+ if (signal) {
58
+ // Re-raise so the caller's exit status reflects the signal.
59
+ process.kill(process.pid, signal);
60
+ } else {
61
+ process.exit(code === null ? 1 : code);
62
+ }
63
+ });
package/install.js ADDED
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ // Postinstall for taken-gh: make sure the real `taken` Python CLI is
3
+ // available, since the bin shims forward to it. Never fails the npm install:
4
+ // every error path prints guidance and exits 0.
5
+ const { spawnSync } = require("node:child_process");
6
+ const pkg = require("./package.json");
7
+ const VERSION = pkg.version;
8
+
9
+ function manualInstallHint() {
10
+ return (
11
+ "You can install it by hand with:\n" +
12
+ ` python3 -m pip install --user "taken-gh==${VERSION}"`
13
+ );
14
+ }
15
+
16
+ // `taken --version` prints e.g. "taken 0.7.4". Returns the version string, or
17
+ // null when taken is missing or its output cannot be parsed.
18
+ function installedVersion() {
19
+ const existing = spawnSync("taken", ["--version"], { encoding: "utf8" });
20
+ if (existing.status !== 0 || existing.error) return null;
21
+ const m = /taken (\d+\.\d+\.\d+[^\s]*)/.exec(existing.stdout || "");
22
+ return m ? m[1] : null;
23
+ }
24
+
25
+ function pipInstall(extraArgs) {
26
+ const args = [
27
+ "-m",
28
+ "pip",
29
+ "install",
30
+ "--user",
31
+ ...extraArgs,
32
+ `taken-gh==${VERSION}`,
33
+ ];
34
+ // stdout stays inherited so pip's progress still shows; stderr is captured
35
+ // so we can spot PEP 668's externally-managed-environment and retry.
36
+ return spawnSync("python3", args, {
37
+ encoding: "utf8",
38
+ stdio: ["inherit", "inherit", "pipe"],
39
+ });
40
+ }
41
+
42
+ function main() {
43
+ try {
44
+ const have = installedVersion();
45
+ if (have === VERSION) {
46
+ console.log(
47
+ `taken ${VERSION} is already installed, skipping the pip install.`
48
+ );
49
+ return;
50
+ }
51
+ if (have === null) {
52
+ console.log(`Installing taken-gh ${VERSION} with pip...`);
53
+ } else {
54
+ console.log(
55
+ `Found taken ${have}, but this package needs ${VERSION}; reinstalling with pip...`
56
+ );
57
+ }
58
+
59
+ let result = pipInstall([]);
60
+ if (result.error && result.error.code === "ENOENT") {
61
+ console.error(
62
+ "python3 was not found on PATH, so the pip install was skipped.\n" +
63
+ manualInstallHint()
64
+ );
65
+ return;
66
+ }
67
+
68
+ if (
69
+ result.status !== 0 &&
70
+ /externally-managed-environment/.test(result.stderr || "")
71
+ ) {
72
+ console.log(
73
+ "pip refused the user install (externally-managed-environment); " +
74
+ "retrying once with --break-system-packages..."
75
+ );
76
+ result = pipInstall(["--break-system-packages"]);
77
+ }
78
+
79
+ if (result.status !== 0) {
80
+ console.error(
81
+ "The pip install did not finish cleanly, so the taken command may not work yet.\n" +
82
+ manualInstallHint()
83
+ );
84
+ return;
85
+ }
86
+
87
+ console.log("taken installed. Run `taken --help` to get started.");
88
+ } catch (err) {
89
+ console.error(`Postinstall hit a snag and skipped the pip install: ${err.message}`);
90
+ console.error(manualInstallHint());
91
+ }
92
+ process.exit(0);
93
+ }
94
+
95
+ main();
package/package.json ADDED
@@ -0,0 +1 @@
1
+ {"//":"version must stay in sync with pyproject.toml for every release","bin":{"taken":"./bin/taken.js","taken-mcp":"./bin/taken-mcp.js"},"bugs":{"url":"https://github.com/RogueAlg0/taken/issues"},"description":"Check whether a GitHub issue is already taken before you volunteer for it.","engines":{"node":">=18"},"files":["bin/","install.js","README.md","LICENSE"],"homepage":"https://github.com/RogueAlg0/taken","keywords":["github","open-source","cli","good-first-issue","hacktoberfest","mcp"],"license":"MIT","name":"taken-gh","repository":{"type":"git","url":"https://github.com/RogueAlg0/taken.git"},"scripts":{"postinstall":"node install.js","test":"node --test \"test/*.test.js\""},"version":"0.7.5"}