santree 0.2.10 → 0.2.12

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.
@@ -14,7 +14,8 @@ import { run, spawnAsync } from "../lib/exec.js";
14
14
  import { resolveAgentBinary } from "../lib/ai.js";
15
15
  import { extractTicketId } from "../lib/git.js";
16
16
  import { getPRTemplate } from "../lib/github.js";
17
- import { renderPrompt, renderDiff } from "../lib/prompts.js";
17
+ import { renderPrompt, renderDiff, renderTicket } from "../lib/prompts.js";
18
+ import { getTicketContent } from "../lib/linear.js";
18
19
  import * as os from "os";
19
20
  import { initialState, reducer } from "../lib/dashboard/types.js";
20
21
  import { loadDashboardData, loadReviewsData } from "../lib/dashboard/data.js";
@@ -719,6 +720,15 @@ export default function Dashboard() {
719
720
  }
720
721
  dispatch({ type: "PR_CREATE_PHASE", phase: "filling" });
721
722
  const ticketId = extractTicketId(s.prCreateBranch) ?? "";
723
+ const mainRepoRoot = findMainRepoRoot();
724
+ // Fetch ticket content (downloads images for Linear tickets)
725
+ let ticketContent;
726
+ if (ticketId && mainRepoRoot) {
727
+ const ticket = await getTicketContent(ticketId, mainRepoRoot);
728
+ if (ticket) {
729
+ ticketContent = renderTicket(ticket);
730
+ }
731
+ }
722
732
  const commitLog = run(`git log ${base}..HEAD --format="- %s"`, { cwd }) || null;
723
733
  const diffStat = run(`git diff ${base}..HEAD --stat`, { cwd }) || null;
724
734
  const diff = run(`git diff ${base}..HEAD`, { cwd, maxBuffer: 10 * 1024 * 1024 }) || null;
@@ -732,10 +742,11 @@ export default function Dashboard() {
732
742
  pr_template: prTemplate,
733
743
  diff_content: diffContent,
734
744
  ticket_id: ticketId,
745
+ ticket_content: ticketContent,
735
746
  branch_name: s.prCreateBranch,
736
747
  });
737
748
  // Pass prompt via stdin instead of temp file
738
- const agentResult = await spawnAsync(bin, ["-p", "--output-format", "text"], {
749
+ const agentResult = await spawnAsync(bin, ["-p", "--output-format", "text", "--allowedTools", "Read"], {
739
750
  stdin: prompt,
740
751
  });
741
752
  const body = agentResult.output.trim();
@@ -10,8 +10,9 @@ import { writeFileSync } from "fs";
10
10
  import { tmpdir } from "os";
11
11
  import { findMainRepoRoot, findRepoRoot, getCurrentBranch, getBaseBranch, hasUncommittedChanges, getCommitsAhead, remoteBranchExists, getUnpushedCommits, extractTicketId, isInWorktree, getFirstCommitMessage, getCommitLog, getDiffStat, getDiffContent, } from "../../lib/git.js";
12
12
  import { ghCliAvailable, getPRInfoAsync, pushBranch, createPR, getPRTemplate, } from "../../lib/github.js";
13
- import { renderPrompt, renderDiff } from "../../lib/prompts.js";
13
+ import { renderPrompt, renderDiff, renderTicket } from "../../lib/prompts.js";
14
14
  import { runAgent } from "../../lib/ai.js";
15
+ import { getTicketContent } from "../../lib/linear.js";
15
16
  const execAsync = promisify(exec);
16
17
  export const description = "Create a GitHub pull request";
17
18
  export const options = z.object({
@@ -45,7 +46,7 @@ export default function PR({ options }) {
45
46
  setPendingCreate(false);
46
47
  openPR();
47
48
  }, [pendingCreate]);
48
- function openPR() {
49
+ async function openPR() {
49
50
  if (!branch || !baseBranch)
50
51
  return;
51
52
  const title = getFirstCommitMessage(baseBranch) ?? branch;
@@ -61,6 +62,15 @@ export default function PR({ options }) {
61
62
  return;
62
63
  }
63
64
  const ticketId = extractTicketId(branch);
65
+ const mainRepoRoot = findMainRepoRoot();
66
+ // Fetch ticket content (downloads images for Linear tickets)
67
+ let ticketContent;
68
+ if (ticketId && mainRepoRoot) {
69
+ const ticket = await getTicketContent(ticketId, mainRepoRoot);
70
+ if (ticket) {
71
+ ticketContent = renderTicket(ticket);
72
+ }
73
+ }
64
74
  const diffContent = renderDiff({
65
75
  base_branch: baseBranch,
66
76
  commit_log: getCommitLog(baseBranch),
@@ -71,9 +81,10 @@ export default function PR({ options }) {
71
81
  pr_template: prTemplate,
72
82
  diff_content: diffContent,
73
83
  ticket_id: ticketId ?? "",
84
+ ticket_content: ticketContent,
74
85
  branch_name: branch,
75
86
  });
76
- const result = runAgent(prompt);
87
+ const result = runAgent(prompt, { allowedTools: ["Read"] });
77
88
  if (!result.success) {
78
89
  setStatus("error");
79
90
  setMessage("Failed to generate PR body with Claude");
package/dist/lib/ai.d.ts CHANGED
@@ -60,7 +60,9 @@ export interface RunAgentResult {
60
60
  * Passes prompt directly or via temp file if too large for OS arg limit.
61
61
  * Throws if claude CLI is not found.
62
62
  */
63
- export declare function runAgent(prompt: string): RunAgentResult;
63
+ export declare function runAgent(prompt: string, opts?: {
64
+ allowedTools?: string[];
65
+ }): RunAgentResult;
64
66
  /**
65
67
  * Cleanup images downloaded for a ticket.
66
68
  */
package/dist/lib/ai.js CHANGED
@@ -165,13 +165,14 @@ export function launchAgent(prompt, opts) {
165
165
  * Passes prompt directly or via temp file if too large for OS arg limit.
166
166
  * Throws if claude CLI is not found.
167
167
  */
168
- export function runAgent(prompt) {
168
+ export function runAgent(prompt, opts) {
169
169
  const bin = resolveAgentBinary();
170
170
  if (!bin) {
171
171
  throw new Error("Claude CLI not found. Install: npm install -g @anthropic-ai/claude-code");
172
172
  }
173
173
  const skipPerms = process.env.SANTREE_SKIP_PERMISSIONS ? ["--dangerously-skip-permissions"] : [];
174
- const result = spawnSync(bin, [...skipPerms, "-p", "--output-format", "text", "--", promptArg(prompt)], {
174
+ const toolArgs = opts?.allowedTools?.length ? ["--allowedTools", ...opts.allowedTools] : [];
175
+ const result = spawnSync(bin, [...skipPerms, ...toolArgs, "-p", "--output-format", "text", "--", promptArg(prompt)], {
175
176
  encoding: "utf-8",
176
177
  maxBuffer: 10 * 1024 * 1024,
177
178
  });
@@ -116,13 +116,17 @@ export async function startOAuthFlow() {
116
116
  code_challenge_method: "S256",
117
117
  });
118
118
  const authUrl = `${LINEAR_AUTHORIZE_URL}?${params.toString()}`;
119
- // Open browser
119
+ // Try to open browser, fall back to printing URL
120
120
  const openCmd = process.platform === "darwin"
121
121
  ? "open"
122
122
  : process.platform === "win32"
123
123
  ? "start"
124
124
  : "xdg-open";
125
- exec(`${openCmd} "${authUrl}"`);
125
+ exec(`${openCmd} "${authUrl}"`, (err) => {
126
+ if (err) {
127
+ console.error(`\nCouldn't open browser automatically. Open this URL manually:\n${authUrl}\n`);
128
+ }
129
+ });
126
130
  });
127
131
  // Timeout after 2 minutes
128
132
  setTimeout(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "santree",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "description": "Git worktree manager",
5
5
  "license": "MIT",
6
6
  "author": "Santiago Toscanini",
@@ -19,6 +19,6 @@ Branch: {{ branch_name }}
19
19
  - Fill the template sections using the commits and diff above
20
20
  - Be concise and factual
21
21
  - For any screenshot sections, leave a comment: `<!-- Screenshot: describe what to show -->`
22
- - If you lack information to fill a section, fill it with what you have from the diff and commits. If you truly have nothing relevant, leave the section empty. Never write excuses like "I don't have permissions" or "Unable to access" — that is not valid template content.
22
+ - If you lack information to fill a section, fill it with what you have from the diff and commits. If you truly have nothing relevant, leave the section empty.
23
23
  - Do NOT wrap the output in a code block
24
24
  - Output ONLY the filled template