pi-tandem 0.3.0 → 0.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
@@ -15,7 +15,7 @@ Modern coding agents lean toward autonomy: multi-file changes in one go, subagen
15
15
  ## What's inside
16
16
 
17
17
  - **Pair-work prompt patch** - collaboration style (lock-step, explicit go-aheads), terse communication rules, and a "lazy senior" coding discipline: no speculative abstractions, deletion over addition, root-cause fixes
18
- - **`subagent` skill** to run a task in a fresh agent session on request, with full visibility and control, instead of harness-managed subagents
18
+ - **Interactive subagent workflow** to run a task in a fresh session on request (a new tmux window or a background paseo agent), with full visibility and control
19
19
  - **Tool-specific instructions** (`gh`, `aws`, `jira`, ...), added to the prompt only if the tool is actually installed
20
20
 
21
21
  ## Recommended tools
@@ -23,11 +23,14 @@ Modern coding agents lean toward autonomy: multi-file changes in one go, subagen
23
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
24
 
25
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
26
+ - `html2text` (Python, e.g. via `pipx install html2text`) - read web pages as markdown instead of raw markup
27
+ - `lynx` - plain-text fallback for web pages when the Python `html2text` is not installed
27
28
  - `pdftotext` (poppler) - extract text from PDFs
28
29
  - a Chromium-based browser (Brave, Chromium, Chrome, Edge) - headless-render fallback for JS-heavy or bot-blocked pages
30
+ - `pandoc` - convert docx/odt/rtf to markdown
29
31
  - `osascript` (macOS only) - drive the user's real browser sessions via AppleScript
30
32
  - `jira`, `aws`, `saml2aws` - if your workflow includes them
33
+ - `tmux` or `paseo` - run the main session inside one of these to get the interactive subagent workflow
31
34
 
32
35
  ## Attribution
33
36
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-tandem",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/skhoroshavin/tandem-skills.git"
package/prompt.md CHANGED
@@ -46,14 +46,19 @@ A number of CLI tools are installed on this laptop and fully authenticated, you'
46
46
  <!--cli:pdftotext-->
47
47
  - Use `pdftotext` to extract text from PDFs: `pdftotext input.pdf output.txt`, or `pdftotext input.pdf -` to print to stdout
48
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`.
49
+ <!--cli:html2text-->
50
+ - Unless your task requires seeing actual tags, always convert HTML fetched from the web to markdown instead of reading it raw, for example when using `curl`: `curl -s <url> | html2text --ignore-images --no-wrap-links`.
51
+ <!--/cli-->
52
+ <!--cli:lynx-->
53
+ - Unless your task requires seeing actual tags, always convert HTML fetched from the web to plain text instead of reading it raw, for example when using `curl`: `curl -s <url> | lynx -stdin -dump` (link URLs are kept in a References list).
52
54
  <!--/cli-->
53
55
  <!--cli:browser-->
54
56
  - 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
57
  - When in doubt, always try `curl` first before resorting to a headless browser
56
58
  <!--/cli-->
59
+ <!--cli:pandoc-->
60
+ - Use `pandoc` to convert documents (docx, odt, rtf, ...) to markdown: `pandoc input.docx -o output.md`
61
+ <!--/cli-->
57
62
  <!--cli:osascript-->
58
63
  - Use `osascript` with `execute <tab> javascript "<js>"` to read pages and interact using the user's
59
64
  real logged-in sessions (analyzing dashboards, checking Google Calendar and Mail, etc)
@@ -64,6 +69,33 @@ Important:
64
69
  - Read-only commands, like checking state of GHA workflow or reading web page content, are fine without asking
65
70
  - Commands with side effects, like push, create/modify/delete of repos, issues, PRs, releases, triggering workflows, submitting forms, posting or purchasing require an explicit go-ahead, like any other side-effect, as stated in the collaboration style section
66
71
 
72
+ <!--cli:tmux-->
73
+ When explicitly instructed to run a task in a separate or fresh agent session ("do it in a fresh session", "use a subagent"), spawn a worker in a new tmux window with the bundled script. Pass the full task on stdin, self-contained - the worker never sees this session's history:
74
+
75
+ ```bash
76
+ <spawn-script> <task-name> <<'EOF'
77
+ <full task>
78
+ EOF
79
+ ```
80
+
81
+ - If asked for a specific model, pass it after the task name: `<spawn-script> <task-name> <model>`
82
+ - The script opens a tmux window named `<task-name>` running `pi --no-session`, sends the task to it together with instructions for reporting back after user approval, and prints how to terminate the worker
83
+ - After spawning, stop and wait: do not poll the worker pane and do not read the result file early. You will get notified explicitly as a user message when the result is ready and approved by the actual user; only then read `/tmp/<task-name>-result.md` and clean up the window
84
+ <!--/cli-->
85
+ <!--cli:paseo-->
86
+ When explicitly instructed to run a task in a separate or fresh agent session ("do it in a fresh session", "use a subagent"), spawn a background paseo worker with the bundled script. Pass the full task on stdin, self-contained - the worker never sees this session's history:
87
+
88
+ ```bash
89
+ <spawn-script> <task-name> <<'EOF'
90
+ <full task>
91
+ EOF
92
+ ```
93
+
94
+ - If asked for a specific model, pass it after the task name: `<spawn-script> <task-name> <model>`
95
+ - The script starts a background paseo agent titled `<task-name>`, sends the task to it together with instructions for reporting back after user approval, and prints how to terminate the worker
96
+ - After spawning, stop and wait: do not poll the worker and do not read the result file early. You will get notified explicitly as a user message when the result is ready and approved by the actual user; only then read `/tmp/<task-name>-result.md` and clean up (`paseo archive --force <agent-id>`)
97
+ <!--/cli-->
98
+
67
99
  ## Coding tasks
68
100
 
69
101
  When working on coding tasks, you are a lazy senior developer. Lazy means efficient, not careless. The best code is the code never written. Stop at the first rung that holds:
@@ -1,8 +1,15 @@
1
1
  // Shared runtime helpers, copied verbatim into every package's runtime/ dir.
2
2
  // Files filtered by this module must contain <!--cli:tool-->...<!--/cli--> blocks.
3
3
 
4
+ import { spawnSync } from "node:child_process";
4
5
  import { statSync } from "node:fs";
5
- import { delimiter, join } from "node:path";
6
+ import { delimiter, dirname, join } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const spawnScript = join(
10
+ dirname(fileURLToPath(import.meta.url)),
11
+ process.env.PASEO_AGENT_ID ? "spawn-paseo.sh" : "spawn-tmux.sh",
12
+ );
6
13
 
7
14
  // windows executables are found via fixed suffixes (.exe etc)
8
15
  const extensions = process.platform === "win32" ? [".EXE", ".BAT", ".CMD"] : [""];
@@ -33,26 +40,46 @@ function findBrowserBinary() {
33
40
  }
34
41
  }
35
42
 
43
+ // brew/apt ship a different C++-based html2text with an incompatible CLI;
44
+ // only the Python one supports the --ignore-images flag used in the prompt
45
+ function isPythonHtml2text() {
46
+ if (!isOnPath("html2text")) return false;
47
+ try {
48
+ const res = spawnSync("html2text", ["--help"], { encoding: "utf8", timeout: 5000 });
49
+ return res.status === 0 && (res.stdout || "").includes("--ignore-images");
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
54
+
36
55
  // 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;
56
+ function toolAvailable(tool, ctx) {
57
+ if (tool === "browser") return ctx.browser !== undefined;
58
+ if (tool === "html2text") return ctx.html2text;
59
+ if (tool === "lynx") return !ctx.html2text && isOnPath(tool);
39
60
  if (tool === "osascript") return process.platform === "darwin" && isOnPath(tool);
61
+ if (tool === "paseo") return !!process.env.PASEO_AGENT_ID;
62
+ if (tool === "tmux") return !process.env.PASEO_AGENT_ID && !!process.env.TMUX;
40
63
  return isOnPath(tool);
41
64
  }
42
65
 
43
66
  export function filterCliTools(raw) {
44
67
  let hasTools = false;
45
- const browser = findBrowserBinary();
68
+ const ctx = {
69
+ browser: findBrowserBinary(),
70
+ html2text: isPythonHtml2text(),
71
+ };
46
72
  const filtered = raw
47
73
  .replace(
48
74
  /<!--cli:([a-z0-9]+)-->\r?\n?([\s\S]*?)<!--\/cli-->\r?\n?/g,
49
75
  (_marker, tool, body) => {
50
- if (!toolAvailable(tool, browser)) return "";
76
+ if (!toolAvailable(tool, ctx)) return "";
51
77
  hasTools = true;
52
78
  return body;
53
79
  },
54
80
  )
55
- .replace(/<browser-binary>/g, () => (browser ? JSON.stringify(browser) : "<browser-binary>"))
81
+ .replace(/<browser-binary>/g, () => (ctx.browser ? JSON.stringify(ctx.browser) : "<browser-binary>"))
82
+ .replace(/<spawn-script>/g, () => JSON.stringify(spawnScript))
56
83
  .replace(/\n{3,}/g, "\n\n");
57
84
  return hasTools ? filtered : filtered.replace(/\n## CLI tools[\s\S]*?(?=\n## )/, "");
58
85
  }
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env bash
2
+ # Spawn a fresh worker agent session in the background via the local paseo daemon.
3
+ # Usage: spawn-paseo.sh <task-name> [<model>] (full task on stdin)
4
+ set -euo pipefail
5
+
6
+ name="${1:?usage: spawn-paseo.sh <task-name> [<model>] (full task on stdin)}"
7
+ model="${2:-}"
8
+ [[ $# -le 2 ]] || { echo "error: unexpected args: $*" >&2; exit 2; }
9
+ [[ "$name" =~ ^[a-z0-9][a-z0-9-]{0,30}$ ]] || { echo "error: bad name" >&2; exit 2; }
10
+ [[ -n "${PASEO_AGENT_ID:-}" ]] || { echo "error: not inside a paseo agent session" >&2; exit 2; }
11
+
12
+ task="$(cat)"
13
+ [[ -n "$task" ]] || { echo "error: empty task on stdin" >&2; exit 2; }
14
+
15
+ # the name keys the result file: keep it unique among live workers
16
+ result="/tmp/${name}-result.md"
17
+
18
+ prompt="$task
19
+
20
+ # Rules
21
+
22
+ Check whether you have some skills applicable to this task, and load
23
+ them before starting working. When done, write the full result to
24
+ $result, tell the user it is ready for review and wait for further
25
+ instructions. Only after the user explicitly approves, notify the
26
+ parent with a one-line pointer (never result content):
27
+
28
+ paseo send --no-wait '$PASEO_AGENT_ID' 'Result ready: $result'"
29
+
30
+ # a crashed run may have left a stale result for this name
31
+ rm -f "$result"
32
+ if [[ -n "$model" ]]; then
33
+ id="$(paseo run -q --background --title "$name" --provider pi --model "$model" "$prompt")"
34
+ else
35
+ id="$(paseo run -q --background --title "$name" --provider pi "$prompt")"
36
+ fi
37
+ [[ -n "$id" ]] || { echo "error: paseo run did not return an agent id" >&2; exit 1; }
38
+
39
+ echo "Worker spawned as paseo agent \"$id\". Terminate with: paseo archive --force \"$id\""
@@ -1,19 +1,11 @@
1
1
  #!/usr/bin/env bash
2
2
  # Spawn a fresh worker agent session in a new tmux window.
3
- # Usage: spawn-subagent.sh <task-name> [--model <model>] (full task on stdin)
3
+ # Usage: spawn-tmux.sh <task-name> [<model>] (full task on stdin)
4
4
  set -euo pipefail
5
5
 
6
- name="${1:?usage: spawn-subagent.sh <task-name> [--model <model>] (full task on stdin)}"
7
- shift
8
- model_flag=""
9
- if [[ "${1:-}" == "--model" ]]; then
10
- [[ $# -ge 2 ]] || { echo "error: --model needs a value" >&2; exit 2; }
11
- # plain model ids only: the flag is embedded in a tmux command string
12
- [[ "$2" =~ ^[A-Za-z0-9._:/-]+$ ]] || { echo "error: bad model" >&2; exit 2; }
13
- model_flag=" --model $2"
14
- shift 2
15
- fi
16
- [[ $# -eq 0 ]] || { echo "error: unexpected args: $*" >&2; exit 2; }
6
+ name="${1:?usage: spawn-tmux.sh <task-name> [<model>] (full task on stdin)}"
7
+ model="${2:-}"
8
+ [[ $# -le 2 ]] || { echo "error: unexpected args: $*" >&2; exit 2; }
17
9
  [[ "$name" =~ ^[a-z0-9][a-z0-9-]{0,30}$ ]] || { echo "error: bad name" >&2; exit 2; }
18
10
  [[ -n "${TMUX:-}" ]] || { echo "error: not inside tmux" >&2; exit 2; }
19
11
 
@@ -45,8 +37,11 @@ prompt=${prompt//\'/\'\\\'\'}
45
37
 
46
38
  # a crashed run may have left a stale result for this name
47
39
  rm -f "$result"
48
- tmux new-window -c "$PWD" -n "$name" \
49
- "pi --no-session$model_flag '$prompt'"
40
+ if [[ -n "$model" ]]; then
41
+ tmux new-window -c "$PWD" -n "$name" "pi --no-session --model $model '$prompt'"
42
+ else
43
+ tmux new-window -c "$PWD" -n "$name" "pi --no-session '$prompt'"
44
+ fi
50
45
  tmux set-option -w -t "$name" automatic-rename off
51
46
 
52
47
  echo "Worker spawned in tmux window \"$name\". Terminate with: tmux kill-window -t $name"
@@ -3,19 +3,13 @@ name: code-review
3
3
  description: Use when asked to do a code review.
4
4
  ---
5
5
 
6
- Read the full diff and everything it touches before judging; the worst review
7
- mistakes come from reviewing the diff only.
6
+ Read the full diff and everything it touches before judging; the worst review mistakes come from reviewing the diff only. The review is read-only: never modify tracked files, the index, HEAD, or branch state; running builds and tests to verify the change is fine.
7
+
8
+ Review as a lazy senior engineer: besides bugs, flag NIH syndrome, overengineering, unnecessary dependencies, obvious copy paste and other similar code bloats. Give accompanying tests the same, if not higher, level of attention: unreadable, overcomplicated tests explain nothing, and a test that would also pass without the change proves nothing.
8
9
 
9
10
  Report rules:
10
11
 
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.
12
+ - Actionable findings only, ordered by severity: ones requiring changes, or 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 smallest fix as a concrete diff, or set of diffs; if not diffable, describe generally and ask for a decision.
14
+ - No praise, no diff summary, no restating the request, no advice beyond findings.
18
15
  - 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.
@@ -1,29 +0,0 @@
1
- ---
2
- name: subagent
3
- description: Use when explicitly instructed to run a task in a separate or fresh agent session - e.g. "do it in a fresh session", "use a subagent".
4
- ---
5
-
6
- Spawn a subagent in a new tmux window using the script bundled next to this
7
- file. Pass the full task on stdin, self-contained - the subagent never sees
8
- this session's history:
9
-
10
- ```bash
11
- <this-skill-dir>/spawn-subagent.sh <task-name> <<'EOF'
12
- <full task>
13
- EOF
14
- ```
15
-
16
- After spawning, stop and wait: do not poll the worker pane and do not read
17
- the result file early. You will get notified explicitly as a user message
18
- when the result is ready and approved by the actual user; only then read
19
- the file and clean up the window.
20
-
21
- If asked to use a specific model, pass it with `--model <model>`:
22
-
23
- ```bash
24
- <this-skill-dir>/spawn-subagent.sh <task-name> --model <model> <<'EOF'
25
- <full task>
26
- EOF
27
- ```
28
-
29
- Workers run `pi --no-session`.