Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ AI-powered talent profile search using natural language. CLI tool with interacti

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

### Install from npm
### Install and run

```bash
npm install -g talent-agent
npm install -g talent-agent && talent-agent
```

This installs the CLI globally and launches the interactive TUI. If Bun is not installed, you will be prompted to install it.

Or run without installing:

```bash
Expand Down
49 changes: 37 additions & 12 deletions scripts/postinstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,27 @@
*/

import { execFileSync } from "node:child_process";
import { openSync, writeSync, closeSync } from "node:fs";

// npm v7+ suppresses all lifecycle script stdout/stderr for successful
// scripts. Writing directly to /dev/tty bypasses that capture and
// prints straight to the user's terminal. Falls back to stderr when
// /dev/tty is unavailable (CI, Docker, non-interactive shells).
let ttyFd;
try {
ttyFd = openSync("/dev/tty", "w");
} catch {
// /dev/tty not available -- fall back to stderr
}

const log = (msg = "") => {
const line = msg + "\n";
if (ttyFd !== undefined) {
writeSync(ttyFd, line);
} else {
process.stderr.write(line);
}
};

let hasBun = false;
try {
Expand All @@ -19,19 +40,23 @@ try {
// bun not found -- warn below
}

console.log("");
console.log(" talent-agent installed successfully!");
console.log("");
log();
log(" talent-agent installed successfully!");
log();

if (!hasBun) {
console.log(" Bun runtime is required but was not found.");
console.log(" Install it: curl -fsSL https://bun.sh/install | bash");
console.log(" Then restart your terminal (or run: source ~/.bashrc)");
console.log("");
log(" Bun runtime is required but was not found.");
log(" Install it: curl -fsSL https://bun.sh/install | bash");
log(" Then restart your terminal (or run: source ~/.bashrc)");
log();
}

console.log(" Get started:");
console.log(" talent-agent login Authenticate");
console.log(' talent-agent "Find devs" Search for talent');
console.log(" talent-agent --help Show all options");
console.log("");
log(" Get started:");
log(" talent-agent login Authenticate");
log(' talent-agent "Find devs" Search for talent');
log(" talent-agent --help Show all options");
log();

if (ttyFd !== undefined) {
closeSync(ttyFd);
}
68 changes: 36 additions & 32 deletions test/postinstall.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Runs the script via `node` as a subprocess to verify its output,
* matching how npm would execute it after `npm install`.
*/
import { execFileSync } from "node:child_process";
import { execFileSync, spawnSync } from "node:child_process";
import { dirname, resolve } from "node:path";
import { describe, expect, it } from "vitest";

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

/**
* Helper to run the postinstall script and capture output.
*
* The script writes to /dev/tty (bypasses npm's stdio suppression) with
* a fallback to stderr. In tests we force the fallback by setting
* FORCE_STDERR=1, which doesn't exist in the script -- instead we
* redirect /dev/tty writes by running without a controlling terminal.
* The simplest reliable approach: pipe the child's stdio so /dev/tty
* open fails, triggering the stderr fallback that we CAN capture.
*/
function runPostinstall(env?: Record<string, string>): {
stdout: string;
stderr: string;
output: string;
exitCode: number;
} {
try {
const stdout = execFileSync("node", [SCRIPT_PATH], {
env: {
...process.env,
...env,
},
encoding: "utf-8",
timeout: 10000,
});
return { stdout, stderr: "", exitCode: 0 };
} catch (error: any) {
return {
stdout: error.stdout ?? "",
stderr: error.stderr ?? "",
exitCode: error.status ?? 1,
};
}
const result = spawnSync("node", [SCRIPT_PATH], {
env: {
...process.env,
...env,
},
encoding: "utf-8",
timeout: 10000,
// Piping stdio detaches the child from the controlling terminal,
// so openSync("/dev/tty") falls back to stderr which we capture.
stdio: ["pipe", "pipe", "pipe"],
});
return {
output: (result.stdout ?? "") + (result.stderr ?? ""),
exitCode: result.status ?? 1,
};
}

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

it("prints the success message", () => {
const { stdout } = runPostinstall();
const { output } = runPostinstall();

expect(stdout).toContain("talent-agent installed successfully!");
expect(output).toContain("talent-agent installed successfully!");
});

it("prints getting-started instructions", () => {
const { stdout } = runPostinstall();
const { output } = runPostinstall();

expect(stdout).toContain("talent-agent login");
expect(stdout).toContain("talent-agent --help");
expect(stdout).toContain("Get started:");
expect(output).toContain("talent-agent login");
expect(output).toContain("talent-agent --help");
expect(output).toContain("Get started:");
});

it("does not show the bun warning when bun is available", () => {
const { stdout } = runPostinstall();
const { output } = runPostinstall();

// bun is available in this dev environment
expect(stdout).not.toContain("Bun runtime is required but was not found");
expect(output).not.toContain("Bun runtime is required but was not found");
});

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

expect(stdout).toContain("Bun runtime is required but was not found");
expect(stdout).toContain("curl -fsSL https://bun.sh/install | bash");
expect(stdout).toContain("restart your terminal");
expect(output).toContain("Bun runtime is required but was not found");
expect(output).toContain("curl -fsSL https://bun.sh/install | bash");
expect(output).toContain("restart your terminal");
});
});