askpplx 1.3.0 → 1.4.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
@@ -29,6 +29,15 @@ askpplx "Explain Raft vs Paxos in simple terms"
29
29
 
30
30
  # web-enabled search with local context
31
31
  askpplx "What are breaking changes in React 19 that affect this code? $(cat src/app.tsx)"
32
+
33
+ # read prompt from stdin (filter style)
34
+ cat article.txt | askpplx -S "Summarize this article"
35
+
36
+ # extract plain text from JSON response
37
+ askpplx "Node.js LTS version" --json | jq -r '.text'
38
+
39
+ # list cited source URLs
40
+ askpplx "Latest TypeScript release notes" --json | jq -r '.sources[].url' | sort -u
32
41
  ```
33
42
 
34
43
  ## Agent Rule
package/dist/cli.js CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from "commander";
2
+ import { Command, Option } from "@commander-js/extra-typings";
3
3
  import packageJson from "../package.json" with { type: "json" };
4
4
  import { clearPerplexityApiKey, getConfigPath, getPerplexityApiKey, maskApiKey, setPerplexityApiKey, } from "./config.js";
5
5
  import { runCli } from "./run-cli.js";
6
+ import { collectStdinText } from "./collect-stdin-text.js";
7
+ import { resolveCliPrompt } from "./resolve-cli-prompt.js";
6
8
  const usageExamples = `
7
9
  About Perplexity:
8
10
  Perplexity AI is an AI-powered search engine and answer engine that delivers
@@ -28,28 +30,51 @@ Examples:
28
30
  askpplx "Latest news on AI" -c medium
29
31
  askpplx "$(cat article.txt)" -s ./summarize.md
30
32
  askpplx "$(cat article.txt)" -S "Summarize this article"
33
+ cat article.txt | askpplx -S "Summarize this article"
31
34
  askpplx "Node.js LTS version" --json | jq -r '.text'
32
35
  askpplx "Show reasoning" --show-thinking`;
33
36
  const program = new Command()
34
- .name("askpplx")
37
+ .name(packageJson.name)
35
38
  .description(packageJson.description)
36
39
  .version(packageJson.version)
40
+ .showHelpAfterError("(add --help for additional information)")
41
+ .showSuggestionAfterError()
37
42
  .argument("[prompt]", "The prompt to send to Perplexity Sonar")
38
43
  .option("-m, --model <model>", "Model to use", "sonar-reasoning-pro")
39
44
  .option("-s, --system <path>", "Path to custom system prompt file")
40
45
  .option("-S, --system-text <text>", "System prompt text (overrides -s)")
41
- .option("-c, --context <size>", "Search context size: low, medium, high", "high")
46
+ .addOption(new Option("-c, --context <size>", "Search context size: low, medium, high")
47
+ .choices(["low", "medium", "high"])
48
+ .default("high"))
42
49
  .option("--json", "Output full API response as JSON (text, sources, usage)")
43
50
  .option("--show-thinking", "Show model thinking/reasoning blocks")
44
- .option("--no-stream, --no-streaming", "Disable streaming output")
51
+ .option("--no-stream", "Disable streaming output")
52
+ .addOption(new Option("--no-streaming", "Alias for --no-stream").hideHelp())
45
53
  .addHelpText("after", usageExamples)
46
54
  .action(async (prompt, options) => {
47
- if (!prompt) {
48
- program.help();
49
- return;
50
- }
51
55
  try {
52
- await runCli(prompt, options);
56
+ let stdinText;
57
+ if (!prompt && !process.stdin.isTTY) {
58
+ process.stdin.setEncoding("utf8");
59
+ stdinText = await collectStdinText(process.stdin);
60
+ }
61
+ const effectivePrompt = resolveCliPrompt(prompt, stdinText);
62
+ if (!effectivePrompt) {
63
+ program.error("Missing prompt.\n" +
64
+ 'Usage: askpplx <prompt> OR cat <file> | askpplx -S "Summarize this article"\n' +
65
+ "(Note: use -S to provide an instruction with stdin input)", { exitCode: 1 });
66
+ return;
67
+ }
68
+ const cliOptions = {
69
+ model: options.model,
70
+ json: Boolean(options.json),
71
+ system: options.system,
72
+ systemText: options.systemText,
73
+ context: options.context,
74
+ showThinking: Boolean(options.showThinking),
75
+ stream: options.stream && options.streaming,
76
+ };
77
+ await runCli(effectivePrompt, cliOptions);
53
78
  }
54
79
  catch (error) {
55
80
  const message = error instanceof Error ? error.message : "An unexpected error occurred";
@@ -57,7 +82,7 @@ const program = new Command()
57
82
  process.exitCode = 1;
58
83
  }
59
84
  });
60
- program
85
+ const configCommand = program
61
86
  .command("config")
62
87
  .description("Manage stored configuration")
63
88
  .option("--set-api-key <key>", "Store Perplexity API key")
@@ -68,7 +93,7 @@ program
68
93
  try {
69
94
  if (options.setApiKey) {
70
95
  setPerplexityApiKey(options.setApiKey);
71
- console.log("API key stored successfully.");
96
+ console.error("API key stored successfully.");
72
97
  }
73
98
  else if (options.showApiKey) {
74
99
  const key = getPerplexityApiKey();
@@ -77,14 +102,13 @@ program
77
102
  }
78
103
  else if (options.clearApiKey) {
79
104
  clearPerplexityApiKey();
80
- console.log("API key cleared.");
105
+ console.error("API key cleared.");
81
106
  }
82
107
  else if (options.path) {
83
108
  console.log(getConfigPath());
84
109
  }
85
110
  else {
86
- const configCmd = program.commands.find((c) => c.name() === "config");
87
- configCmd?.help();
111
+ configCommand.help({ error: true });
88
112
  }
89
113
  }
90
114
  catch (error) {
@@ -95,4 +119,4 @@ program
95
119
  process.exitCode = 1;
96
120
  }
97
121
  });
98
- program.parse();
122
+ await program.parseAsync();
@@ -0,0 +1 @@
1
+ export declare function collectStdinText(input: AsyncIterable<string>, maxBytes?: number): Promise<string>;
@@ -0,0 +1,15 @@
1
+ import { Buffer } from "node:buffer";
2
+ const DEFAULT_MAX_STDIN_BYTES = 10 * 1024 * 1024;
3
+ export async function collectStdinText(input, maxBytes = DEFAULT_MAX_STDIN_BYTES) {
4
+ const chunks = [];
5
+ let totalBytes = 0;
6
+ for await (const chunk of input) {
7
+ totalBytes += Buffer.byteLength(chunk, "utf8");
8
+ if (totalBytes > maxBytes) {
9
+ const limitMegabytes = Math.round(maxBytes / 1024 / 1024);
10
+ throw new Error(`Input too large: exceeds ${String(limitMegabytes)}MB limit`);
11
+ }
12
+ chunks.push(chunk);
13
+ }
14
+ return chunks.join("");
15
+ }
@@ -0,0 +1 @@
1
+ export declare function resolveCliPrompt(promptArgument: string | undefined, stdinText?: string): string | undefined;
@@ -0,0 +1,9 @@
1
+ export function resolveCliPrompt(promptArgument, stdinText) {
2
+ const candidate = (promptArgument ?? stdinText)?.trimEnd();
3
+ if (candidate === undefined)
4
+ return undefined;
5
+ const trimmedCandidate = candidate.trim();
6
+ if (trimmedCandidate.length === 0)
7
+ return undefined;
8
+ return candidate;
9
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "askpplx",
3
3
  "author": "Łukasz Jerciński",
4
4
  "license": "MIT",
5
- "version": "1.3.0",
5
+ "version": "1.4.0",
6
6
  "description": "Minimal Unix-style CLI for querying Perplexity Sonar API.",
7
7
  "repository": {
8
8
  "type": "git",
@@ -19,21 +19,21 @@
19
19
  "LICENSE"
20
20
  ],
21
21
  "scripts": {
22
- "start": "pnpm -s run rebuild && node --env-file=.env bin/askpplx",
22
+ "prepare": "git config core.hooksPath .githooks",
23
+ "prepublishOnly": "pnpm run rebuild",
23
24
  "build": "tsc -p tsconfig.app.json && cp -r src/prompts dist/",
24
25
  "clean": "rm -rf dist *.tsbuildinfo",
25
- "rebuild": "pnpm run clean && pnpm run build",
26
- "prepare": "husky",
27
- "prepublishOnly": "pnpm run rebuild",
28
- "typecheck": "tsc -b --noEmit",
29
26
  "format": "prettier --write .",
30
27
  "format:check": "prettier --check .",
28
+ "fta": "fta-check",
29
+ "knip": "knip",
31
30
  "lint": "eslint",
31
+ "rebuild": "pnpm run clean && pnpm run build",
32
+ "start": "pnpm -s run rebuild && node --env-file=.env bin/askpplx",
32
33
  "test": "vitest run",
33
- "test:watch": "vitest",
34
34
  "test:coverage": "vitest run --coverage",
35
- "knip": "knip",
36
- "fta:check": "fta-check"
35
+ "test:watch": "vitest",
36
+ "typecheck": "tsc -b --noEmit"
37
37
  },
38
38
  "keywords": [],
39
39
  "packageManager": "pnpm@10.24.0",
@@ -42,14 +42,13 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@ai-sdk/perplexity": "^2.0.21",
45
+ "@commander-js/extra-typings": "^14.0.0",
45
46
  "ai": "^5.0.108",
46
47
  "commander": "^14.0.2",
47
48
  "conf": "^15.0.2",
48
49
  "zod": "^4.1.13"
49
50
  },
50
51
  "devDependencies": {
51
- "@commitlint/cli": "^20.2.0",
52
- "@commitlint/config-conventional": "^20.2.0",
53
52
  "@eslint/compat": "^2.0.0",
54
53
  "@eslint/js": "^9.39.1",
55
54
  "@total-typescript/ts-reset": "^0.6.1",
@@ -62,7 +61,6 @@
62
61
  "fta-check": "^1.2.0",
63
62
  "fta-cli": "^3.0.0",
64
63
  "globals": "^16.5.0",
65
- "husky": "^9.1.7",
66
64
  "knip": "^5.71.0",
67
65
  "prettier": "3.7.4",
68
66
  "semantic-release": "^25.0.2",