New:Socket for Asana Is Now Available.Learn more
Get Started

ashby-cli

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ashby-cli - npm Package Compare versions

Comparing version
0.1.1
to
0.1.2
+42
dist/auth-setup.js
import { spawn } from "node:child_process";
export const ASHBY_API_KEYS_URL = "https://app.ashbyhq.com/admin/api/keys";
export const RECOMMENDED_PERMISSIONS = [
"Jobs: read",
"Candidates: read + write",
"Interviews: read + write",
"Hiring Process: read",
"API Keys: read",
];
export function buildAuthSetupInstructions() {
return [
"Ashby uses API keys, not OAuth.",
`Open this page: ${ASHBY_API_KEYS_URL}`,
"Create an API key with these permissions:",
...RECOMMENDED_PERMISSIONS.map((permission) => `- ${permission}`),
"Optional toggles if you need them:",
"- Allow access to confidential jobs and projects",
"- Allow access to non-offer private fields",
"Then paste the key here so ashby-cli can save and validate it.",
].join("\n");
}
export function getBrowserOpenCommand(url, platform = process.platform) {
if (platform === "darwin") {
return { command: "open", args: [url] };
}
if (platform === "win32") {
return { command: "cmd", args: ["/c", "start", "", url] };
}
return { command: "xdg-open", args: [url] };
}
export async function openBrowser(url, platform = process.platform) {
const { command, args } = getBrowserOpenCommand(url, platform);
return await new Promise((resolve) => {
const child = spawn(command, args, { stdio: "ignore" });
child.on("error", (error) => {
resolve({ ok: false, command, error: error.message });
});
child.on("spawn", () => {
resolve({ ok: true, command });
});
});
}
+83
-1
#!/usr/bin/env node
import { createRequire } from "node:module";
import { createInterface } from "node:readline/promises";
import { Command } from "commander";
import { stdin as input, stderr as output } from "node:process";
import { saveAndValidateApiKey, validateApiKey } from "./auth.js";
import { ASHBY_API_KEYS_URL, buildAuthSetupInstructions, openBrowser } from "./auth-setup.js";
import { AshbyApiClient, AshbyApiError } from "./ashby-api.js";

@@ -33,3 +36,3 @@ import { clearConfig, readConfig, redactApiKey, resolveApiKey } from "./config.js";

else
process.stderr.write("No API key. Use `ashby auth set --stdin` or export `ASHBY_API_KEY`.\n");
process.stderr.write("No API key. Use `ashby auth setup`, `ashby auth set --stdin`, or export `ASHBY_API_KEY`.\n");
process.exitCode = 2;

@@ -80,2 +83,81 @@ return "";

auth
.command("setup")
.description("Open the Ashby API key page and save a pasted API key")
.option("--stdin", "Read the API key from stdin instead of prompting")
.option("--no-open", "Do not open the Ashby admin page in a browser")
.option("--open-only", "Only open the Ashby admin page and print setup guidance")
.option("--json", "Emit JSON output")
.action(async (opts) => {
const resolved = await resolveApiKey();
const existingValidation = resolved ? await validateApiKey(resolved) : undefined;
const instructions = buildAuthSetupInstructions();
const browser = opts.open === false
? { attempted: false, ok: false, command: null, error: undefined }
: await openBrowser(ASHBY_API_KEYS_URL);
const browserMeta = {
attempted: opts.open !== false,
ok: browser.ok,
command: browser.command,
url: ASHBY_API_KEYS_URL,
error: browser.ok ? undefined : browser.error,
};
if (opts.openOnly) {
if (opts.json) {
printJson(ok({
alreadyConfigured: Boolean(resolved),
existingValidation,
browser: browserMeta,
instructions,
}));
}
else {
process.stderr.write(`${instructions}\n`);
}
return;
}
let apiKey = "";
if (opts.stdin) {
apiKey = await readStdin();
}
else if (input.isTTY) {
const rl = createInterface({ input, output });
process.stderr.write(`${instructions}\n`);
apiKey = await rl.question("Paste Ashby API key: ");
rl.close();
}
else {
const error = makeError(null, {
code: "AUTH_MISSING",
message: "No interactive terminal. Run `ashby auth setup --stdin`, `ashby auth set --stdin`, or export `ASHBY_API_KEY`.",
});
if (opts.json) {
printJson(fail(error, { browser: browserMeta, instructions }));
}
else {
process.stderr.write(`${instructions}\n${error.message}\n`);
}
process.exitCode = 2;
return;
}
if (!apiKey.trim()) {
const error = makeError(null, { code: "VALIDATION", message: "No API key provided." });
if (opts.json)
printJson(fail(error, { browser: browserMeta }));
else
process.stderr.write("No API key provided.\n");
process.exitCode = 2;
return;
}
const result = await saveAndValidateApiKey(apiKey);
const payload = {
apiKeyRedacted: redactApiKey(result.apiKey),
validation: result.validation,
browser: browserMeta,
};
if (opts.json)
printJson(ok(payload));
else
console.log(`Saved API key ${payload.apiKeyRedacted}`);
});
auth
.command("set")

@@ -82,0 +164,0 @@ .description("Store an Ashby API key from stdin")

+1
-1
{
"name": "ashby-cli",
"version": "0.1.1",
"version": "0.1.2",
"description": "Agent-first CLI for Ashby's official API",

@@ -5,0 +5,0 @@ "license": "MIT",

@@ -16,2 +16,5 @@ # ashby-cli

Package name: `ashby-cli`
Binary name: `ashby`
```bash

@@ -21,2 +24,8 @@ npm install -g ashby-cli

If you do not want a global install, invoke the published package directly:
```bash
npx -y ashby-cli doctor --json
```
Or from source:

@@ -35,2 +44,4 @@

Ashby does not expose OAuth for this API. The easiest human setup path is browser-assisted API key creation.
You can either:

@@ -42,2 +53,3 @@

```bash
ashby auth setup
ashby auth set --stdin

@@ -48,2 +60,24 @@ ashby auth status

If you are using `npx`, remember it will not load `.env.local` automatically. Export `ASHBY_API_KEY` first or source your env file in the shell.
### Recommended first-time setup
```bash
ashby auth setup
```
This will:
- open the Ashby API key admin page
- remind you which permissions to enable
- accept a pasted API key
- save it locally
- validate it immediately
If you prefer not to install globally:
```bash
npx -y ashby-cli auth setup
```
## Commands

@@ -50,0 +84,0 @@