Skip to content

Commit 57515bb

Browse files
docs: recommend install-and-run command, improve postinstall visibility (#18)
Update the README install instructions to `npm install -g talent-agent && talent-agent` so users get immediate feedback after installing -- either the interactive TUI launches or the bin wrapper shows a clear "install Bun" message. Also write postinstall output to /dev/tty (with stderr fallback) so the welcome message bypasses npm v7+'s lifecycle output suppression in real terminals. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 0ce0bb1 commit 57515bb

3 files changed

Lines changed: 77 additions & 46 deletions

File tree

‎README.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@ AI-powered talent profile search using natural language. CLI tool with interacti
2828

2929
- [Bun](https://bun.sh/) v1.3+
3030

31-
### Install from npm
31+
### Install and run
3232

3333
```bash
34-
npm install -g talent-agent
34+
npm install -g talent-agent && talent-agent
3535
```
3636

37+
This installs the CLI globally and launches the interactive TUI. If Bun is not installed, you will be prompted to install it.
38+
3739
Or run without installing:
3840

3941
```bash

‎scripts/postinstall.js‎

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,27 @@
1010
*/
1111

1212
import { execFileSync } from "node:child_process";
13+
import { openSync, writeSync, closeSync } from "node:fs";
14+
15+
// npm v7+ suppresses all lifecycle script stdout/stderr for successful
16+
// scripts. Writing directly to /dev/tty bypasses that capture and
17+
// prints straight to the user's terminal. Falls back to stderr when
18+
// /dev/tty is unavailable (CI, Docker, non-interactive shells).
19+
let ttyFd;
20+
try {
21+
ttyFd = openSync("/dev/tty", "w");
22+
} catch {
23+
// /dev/tty not available -- fall back to stderr
24+
}
25+
26+
const log = (msg = "") => {
27+
const line = msg + "\n";
28+
if (ttyFd !== undefined) {
29+
writeSync(ttyFd, line);
30+
} else {
31+
process.stderr.write(line);
32+
}
33+
};
1334

1435
let hasBun = false;
1536
try {
@@ -19,19 +40,23 @@ try {
1940
// bun not found -- warn below
2041
}
2142

22-
console.log("");
23-
console.log(" talent-agent installed successfully!");
24-
console.log("");
43+
log();
44+
log(" talent-agent installed successfully!");
45+
log();
2546

2647
if (!hasBun) {
27-
console.log(" Bun runtime is required but was not found.");
28-
console.log(" Install it: curl -fsSL https://bun.sh/install | bash");
29-
console.log(" Then restart your terminal (or run: source ~/.bashrc)");
30-
console.log("");
48+
log(" Bun runtime is required but was not found.");
49+
log(" Install it: curl -fsSL https://bun.sh/install | bash");
50+
log(" Then restart your terminal (or run: source ~/.bashrc)");
51+
log();
3152
}
3253

33-
console.log(" Get started:");
34-
console.log(" talent-agent login Authenticate");
35-
console.log(' talent-agent "Find devs" Search for talent');
36-
console.log(" talent-agent --help Show all options");
37-
console.log("");
54+
log(" Get started:");
55+
log(" talent-agent login Authenticate");
56+
log(' talent-agent "Find devs" Search for talent');
57+
log(" talent-agent --help Show all options");
58+
log();
59+
60+
if (ttyFd !== undefined) {
61+
closeSync(ttyFd);
62+
}

‎test/postinstall.test.ts‎

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Runs the script via `node` as a subprocess to verify its output,
55
* matching how npm would execute it after `npm install`.
66
*/
7-
import { execFileSync } from "node:child_process";
7+
import { execFileSync, spawnSync } from "node:child_process";
88
import { dirname, resolve } from "node:path";
99
import { describe, expect, it } from "vitest";
1010

@@ -25,29 +25,33 @@ function pathWithoutBun(): string {
2525

2626
/**
2727
* Helper to run the postinstall script and capture output.
28+
*
29+
* The script writes to /dev/tty (bypasses npm's stdio suppression) with
30+
* a fallback to stderr. In tests we force the fallback by setting
31+
* FORCE_STDERR=1, which doesn't exist in the script -- instead we
32+
* redirect /dev/tty writes by running without a controlling terminal.
33+
* The simplest reliable approach: pipe the child's stdio so /dev/tty
34+
* open fails, triggering the stderr fallback that we CAN capture.
2835
*/
2936
function runPostinstall(env?: Record<string, string>): {
30-
stdout: string;
31-
stderr: string;
37+
output: string;
3238
exitCode: number;
3339
} {
34-
try {
35-
const stdout = execFileSync("node", [SCRIPT_PATH], {
36-
env: {
37-
...process.env,
38-
...env,
39-
},
40-
encoding: "utf-8",
41-
timeout: 10000,
42-
});
43-
return { stdout, stderr: "", exitCode: 0 };
44-
} catch (error: any) {
45-
return {
46-
stdout: error.stdout ?? "",
47-
stderr: error.stderr ?? "",
48-
exitCode: error.status ?? 1,
49-
};
50-
}
40+
const result = spawnSync("node", [SCRIPT_PATH], {
41+
env: {
42+
...process.env,
43+
...env,
44+
},
45+
encoding: "utf-8",
46+
timeout: 10000,
47+
// Piping stdio detaches the child from the controlling terminal,
48+
// so openSync("/dev/tty") falls back to stderr which we capture.
49+
stdio: ["pipe", "pipe", "pipe"],
50+
});
51+
return {
52+
output: (result.stdout ?? "") + (result.stderr ?? ""),
53+
exitCode: result.status ?? 1,
54+
};
5155
}
5256

5357
describe("scripts/postinstall.js", () => {
@@ -58,31 +62,31 @@ describe("scripts/postinstall.js", () => {
5862
});
5963

6064
it("prints the success message", () => {
61-
const { stdout } = runPostinstall();
65+
const { output } = runPostinstall();
6266

63-
expect(stdout).toContain("talent-agent installed successfully!");
67+
expect(output).toContain("talent-agent installed successfully!");
6468
});
6569

6670
it("prints getting-started instructions", () => {
67-
const { stdout } = runPostinstall();
71+
const { output } = runPostinstall();
6872

69-
expect(stdout).toContain("talent-agent login");
70-
expect(stdout).toContain("talent-agent --help");
71-
expect(stdout).toContain("Get started:");
73+
expect(output).toContain("talent-agent login");
74+
expect(output).toContain("talent-agent --help");
75+
expect(output).toContain("Get started:");
7276
});
7377

7478
it("does not show the bun warning when bun is available", () => {
75-
const { stdout } = runPostinstall();
79+
const { output } = runPostinstall();
7680

7781
// bun is available in this dev environment
78-
expect(stdout).not.toContain("Bun runtime is required but was not found");
82+
expect(output).not.toContain("Bun runtime is required but was not found");
7983
});
8084

8185
it("shows the bun warning when bun is not on PATH", () => {
82-
const { stdout } = runPostinstall({ PATH: pathWithoutBun() });
86+
const { output } = runPostinstall({ PATH: pathWithoutBun() });
8387

84-
expect(stdout).toContain("Bun runtime is required but was not found");
85-
expect(stdout).toContain("curl -fsSL https://bun.sh/install | bash");
86-
expect(stdout).toContain("restart your terminal");
88+
expect(output).toContain("Bun runtime is required but was not found");
89+
expect(output).toContain("curl -fsSL https://bun.sh/install | bash");
90+
expect(output).toContain("restart your terminal");
8791
});
8892
});

0 commit comments

Comments
 (0)