pi-tandem 0.1.0 → 0.3.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
@@ -18,6 +18,17 @@ Modern coding agents lean toward autonomy: multi-file changes in one go, subagen
18
18
  - **`subagent` skill** to run a task in a fresh agent session on request, with full visibility and control, instead of harness-managed subagents
19
19
  - **Tool-specific instructions** (`gh`, `aws`, `jira`, ...), added to the prompt only if the tool is actually installed
20
20
 
21
+ ## Recommended tools
22
+
23
+ The plugin notices which CLI tools are actually installed on your machine, and for each one it finds, adds a short note telling the model that the tool is available and recommended to use. Installing these gets you significantly more out of this package:
24
+
25
+ - `gh` - GitHub CLI: repos, issues, PRs, releases, workflows
26
+ - `pandoc` - read docx/odt/rtf/html (and fetched web pages) as text instead of raw markup
27
+ - `pdftotext` (poppler) - extract text from PDFs
28
+ - a Chromium-based browser (Brave, Chromium, Chrome, Edge) - headless-render fallback for JS-heavy or bot-blocked pages
29
+ - `osascript` (macOS only) - drive the user's real browser sessions via AppleScript
30
+ - `jira`, `aws`, `saml2aws` - if your workflow includes them
31
+
21
32
  ## Attribution
22
33
 
23
34
  The "lazy senior" part of the coding section in the prompt is adapted from [ponytail](https://github.com/DietrichGebert/ponytail) by DietrichGebert (MIT).
@@ -0,0 +1,6 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import injectProjectContext from "./pi-tandem";
3
+
4
+ export default function (pi: ExtensionAPI) {
5
+ injectProjectContext(pi);
6
+ }
@@ -1,11 +1,11 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { filterCliTools } from "../runtime/cli-tools.mjs";
4
+ import { filterCliTools } from "../../runtime/cli-tools.mjs";
5
5
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
6
 
7
7
  const prompt = filterCliTools(
8
- readFileSync(join(dirname(fileURLToPath(import.meta.url)), "../prompt.md"), "utf8"),
8
+ readFileSync(join(dirname(fileURLToPath(import.meta.url)), "../../prompt.md"), "utf8"),
9
9
  );
10
10
 
11
11
  export default function (pi: ExtensionAPI) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-tandem",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/skhoroshavin/tandem-skills.git"
@@ -9,6 +9,7 @@
9
9
  "keywords": [
10
10
  "pi-package"
11
11
  ],
12
+ "type": "module",
12
13
  "license": "MIT",
13
14
  "files": [
14
15
  "extensions/",
@@ -17,5 +18,10 @@
17
18
  "prompt.md",
18
19
  "LICENSE",
19
20
  "README.md"
20
- ]
21
+ ],
22
+ "devDependencies": {
23
+ "@earendil-works/pi-coding-agent": "^0.84.4",
24
+ "@types/node": "^26.4.1",
25
+ "typescript": "^7.0.2"
26
+ }
21
27
  }
package/prompt.md CHANGED
@@ -43,6 +43,17 @@ A number of CLI tools are installed on this laptop and fully authenticated, you'
43
43
  <!--cli:saml2aws-->
44
44
  - AWS credentials are short-lived: on an expired or invalid token error, ask the user to run saml2aws login --idp-account <profile> --skip-prompt themselves, giving them the full command to copy-paste
45
45
  <!--/cli-->
46
+ <!--cli:pdftotext-->
47
+ - Use `pdftotext` to extract text from PDFs: `pdftotext input.pdf output.txt`, or `pdftotext input.pdf -` to print to stdout
48
+ <!--/cli-->
49
+ <!--cli:pandoc-->
50
+ - Use `pandoc` to read documents other than pdf as markdown or plain text: `pandoc input.docx -o output.md` (also works for odt, rtf, html, ...)
51
+ - Unless your task requires seeing actual tags, always convert HTML fetched from the web to plain text or markdown, instead of reading it raw, for example when using `curl`: `curl -s <url> | pandoc -f html -t plain`.
52
+ <!--/cli-->
53
+ <!--cli:browser-->
54
+ - Use a real headless browser when you need to read a JS-rendered or bot-blocked page: `<browser-binary> --headless --disable-gpu --user-data-dir=$(mktemp -d) --dump-dom --virtual-time-budget=5000 <url>`
55
+ - When in doubt, always try `curl` first before resorting to a headless browser
56
+ <!--/cli-->
46
57
  <!--cli:osascript-->
47
58
  - Use `osascript` with `execute <tab> javascript "<js>"` to read pages and interact using the user's
48
59
  real logged-in sessions (analyzing dashboards, checking Google Calendar and Mail, etc)
@@ -18,18 +18,41 @@ export function isOnPath(binary) {
18
18
  return false;
19
19
  }
20
20
 
21
+ // Chromium-family binary for the headless-render fallback; PATH first, then app bundles
22
+ function findBrowserBinary() {
23
+ for (const name of ["brave", "brave-browser", "chromium", "chromium-browser", "google-chrome", "google-chrome-stable"]) {
24
+ if (isOnPath(name)) return name;
25
+ }
26
+ if (process.platform === "darwin") {
27
+ for (const app of ["Brave Browser", "Chromium", "Google Chrome", "Microsoft Edge"]) {
28
+ const bin = `/Applications/${app}.app/Contents/MacOS/${app}`;
29
+ try {
30
+ if (statSync(bin).isFile()) return bin;
31
+ } catch {}
32
+ }
33
+ }
34
+ }
35
+
21
36
  // drop <!--cli:tool--> blocks for absent tools, and the whole section when none remain
37
+ function toolAvailable(tool, browser) {
38
+ if (tool === "browser") return browser !== undefined;
39
+ if (tool === "osascript") return process.platform === "darwin" && isOnPath(tool);
40
+ return isOnPath(tool);
41
+ }
42
+
22
43
  export function filterCliTools(raw) {
23
- let any = false;
24
- const filtered = raw.replace(
25
- /<!--cli:([a-z0-9]+)-->\n([\s\S]*?)<!--\/cli-->\n?/g,
26
- (_marker, tool, body) => {
27
- const available =
28
- tool === "osascript" ? process.platform === "darwin" && isOnPath(tool) : isOnPath(tool);
29
- if (!available) return "";
30
- any = true;
31
- return body;
32
- },
33
- ).replace(/\n{3,}/g, "\n\n");
34
- return any ? filtered : filtered.replace(/\n## CLI tools[\s\S]*?(?=\n## )/, "");
44
+ let hasTools = false;
45
+ const browser = findBrowserBinary();
46
+ const filtered = raw
47
+ .replace(
48
+ /<!--cli:([a-z0-9]+)-->\r?\n?([\s\S]*?)<!--\/cli-->\r?\n?/g,
49
+ (_marker, tool, body) => {
50
+ if (!toolAvailable(tool, browser)) return "";
51
+ hasTools = true;
52
+ return body;
53
+ },
54
+ )
55
+ .replace(/<browser-binary>/g, () => (browser ? JSON.stringify(browser) : "<browser-binary>"))
56
+ .replace(/\n{3,}/g, "\n\n");
57
+ return hasTools ? filtered : filtered.replace(/\n## CLI tools[\s\S]*?(?=\n## )/, "");
35
58
  }
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: code-review
3
+ description: Use when asked to do a code review.
4
+ ---
5
+
6
+ Read the full diff and everything it touches before judging; the worst review
7
+ mistakes come from reviewing the diff only.
8
+
9
+ Report rules:
10
+
11
+ - Actionable findings only, ordered by severity: ones requiring changes, or
12
+ at least explicit decisions; "checked and fine" does not qualify.
13
+ - Each finding: what is wrong and why it matters in 1-3 lines, then the
14
+ smallest fix - as a diff if it fits within 50 LoC, otherwise described
15
+ generally, asking for a decision.
16
+ - No praise, no diff summary, no restating the request, no advice beyond
17
+ findings.
18
+ - If nothing actionable: say exactly that, plus one line on what was checked.
19
+ - Before writing, verify: grep for references to removed or renamed
20
+ identifiers; where the repo has rendered build output, check it is in sync;
21
+ diff the commit message claims against the actual change.