santree 0.0.12 → 0.0.13
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 +7 -0
- package/dist/commands/doctor.js +20 -0
- package/dist/commands/editor.d.ts +10 -0
- package/dist/commands/editor.js +72 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,6 +83,7 @@ santree clean
|
|
|
83
83
|
| `santree work` | Launch Claude AI to work on the current ticket |
|
|
84
84
|
| `santree clean` | Remove worktrees with merged/closed PRs |
|
|
85
85
|
| `santree doctor` | Check system requirements and integrations |
|
|
86
|
+
| `santree editor` | Open workspace file in VSCode or Cursor |
|
|
86
87
|
| `santree statusline` | Statusline wrapper for Claude Code |
|
|
87
88
|
|
|
88
89
|
---
|
|
@@ -173,6 +174,11 @@ Removes the worktree and deletes the branch. Uses force mode by default (removes
|
|
|
173
174
|
### clean
|
|
174
175
|
Shows worktrees with merged/closed PRs and prompts for confirmation before removing.
|
|
175
176
|
|
|
177
|
+
### editor
|
|
178
|
+
| Option | Description |
|
|
179
|
+
|--------|-------------|
|
|
180
|
+
| `--editor <cmd>` | Editor command to use (default: `code`). Also configurable via `SANTREE_EDITOR` env var |
|
|
181
|
+
|
|
176
182
|
### work
|
|
177
183
|
| Option | Description |
|
|
178
184
|
|--------|-------------|
|
|
@@ -190,3 +196,4 @@ Shows worktrees with merged/closed PRs and prompts for confirmation before remov
|
|
|
190
196
|
| Git | Worktree operations |
|
|
191
197
|
| GitHub CLI (`gh`) | PR integration |
|
|
192
198
|
| tmux | Optional: new window support |
|
|
199
|
+
| VSCode (`code`) or Cursor (`cursor`) | Optional: workspace editor |
|
package/dist/commands/doctor.js
CHANGED
|
@@ -228,6 +228,26 @@ export default function Doctor() {
|
|
|
228
228
|
checkTool("claude", "Claude Code CLI", true, "claude --version 2>/dev/null | head -1", "Install: npm install -g @anthropic-ai/claude-code"),
|
|
229
229
|
checkTool("happy", "Claude CLI wrapper", true, "happy --version 2>/dev/null || echo 'installed'", "Install: npm install -g happy-coder"),
|
|
230
230
|
]);
|
|
231
|
+
// Check for either code or cursor (only need one)
|
|
232
|
+
const [codeCheck, cursorCheck] = await Promise.all([
|
|
233
|
+
checkTool("code", "VSCode editor", false, "code --version | head -1", ""),
|
|
234
|
+
checkTool("cursor", "Cursor editor", false, "cursor --version | head -1", ""),
|
|
235
|
+
]);
|
|
236
|
+
if (codeCheck.installed) {
|
|
237
|
+
results.push({ ...codeCheck, description: "Editor (VSCode)" });
|
|
238
|
+
}
|
|
239
|
+
else if (cursorCheck.installed) {
|
|
240
|
+
results.push({ ...cursorCheck, description: "Editor (Cursor)" });
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
results.push({
|
|
244
|
+
name: "code/cursor",
|
|
245
|
+
description: "Editor (VSCode or Cursor)",
|
|
246
|
+
required: false,
|
|
247
|
+
installed: false,
|
|
248
|
+
hint: "Install VSCode (https://code.visualstudio.com) or Cursor (https://cursor.sh)",
|
|
249
|
+
});
|
|
250
|
+
}
|
|
231
251
|
const mcpResult = await checkLinearMcp();
|
|
232
252
|
const statuslineResult = await checkStatusline();
|
|
233
253
|
setTools(results);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const description = "Open workspace file in VSCode or Cursor";
|
|
3
|
+
export declare const options: z.ZodObject<{
|
|
4
|
+
editor: z.ZodOptional<z.ZodString>;
|
|
5
|
+
}, z.core.$strip>;
|
|
6
|
+
type Props = {
|
|
7
|
+
options: z.infer<typeof options>;
|
|
8
|
+
};
|
|
9
|
+
export default function Editor({ options: opts }: Props): import("react/jsx-runtime").JSX.Element | null;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Text, Box } from "ink";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { findMainRepoRoot } from "../lib/git.js";
|
|
6
|
+
import { execSync, spawn } from "child_process";
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
export const description = "Open workspace file in VSCode or Cursor";
|
|
10
|
+
export const options = z.object({
|
|
11
|
+
editor: z
|
|
12
|
+
.string()
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Editor command (code, cursor)"),
|
|
15
|
+
});
|
|
16
|
+
export default function Editor({ options: opts }) {
|
|
17
|
+
const [status, setStatus] = useState({ state: "loading" });
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const repoRoot = findMainRepoRoot();
|
|
20
|
+
if (!repoRoot) {
|
|
21
|
+
setStatus({ state: "error", message: "Not inside a git repository" });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// Find *.code-workspace file
|
|
25
|
+
let workspaceFile = null;
|
|
26
|
+
try {
|
|
27
|
+
const entries = fs.readdirSync(repoRoot);
|
|
28
|
+
const wsFiles = entries
|
|
29
|
+
.filter((f) => f.endsWith(".code-workspace"))
|
|
30
|
+
.sort();
|
|
31
|
+
if (wsFiles.length > 0) {
|
|
32
|
+
workspaceFile = wsFiles[0];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
setStatus({ state: "error", message: "Failed to read repository root" });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (!workspaceFile) {
|
|
40
|
+
setStatus({
|
|
41
|
+
state: "error",
|
|
42
|
+
message: `No .code-workspace file found in ${repoRoot}`,
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// Resolve editor: --editor flag > SANTREE_EDITOR env > "code" default
|
|
47
|
+
const editor = opts.editor || process.env.SANTREE_EDITOR || "code";
|
|
48
|
+
// Validate editor exists in PATH
|
|
49
|
+
try {
|
|
50
|
+
execSync(`which ${editor}`, { stdio: "ignore" });
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
setStatus({
|
|
54
|
+
state: "error",
|
|
55
|
+
message: `Editor "${editor}" not found in PATH`,
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Spawn editor detached
|
|
60
|
+
const fullPath = path.join(repoRoot, workspaceFile);
|
|
61
|
+
const child = spawn(editor, [fullPath], {
|
|
62
|
+
detached: true,
|
|
63
|
+
stdio: "ignore",
|
|
64
|
+
});
|
|
65
|
+
child.unref();
|
|
66
|
+
setStatus({ state: "done", repo: repoRoot, file: workspaceFile, editor });
|
|
67
|
+
}, []);
|
|
68
|
+
if (status.state === "loading") {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, width: "100%", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Editor" }) }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: status.state === "error" ? "red" : "green", paddingX: 1, width: "100%", children: [status.state === "done" && (_jsxs(_Fragment, { children: [_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "repo:" }), _jsx(Text, { dimColor: true, children: status.repo })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "file:" }), _jsx(Text, { color: "cyan", bold: true, children: status.file })] }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "editor:" }), _jsx(Text, { dimColor: true, children: status.editor })] })] })), status.state === "error" && (_jsxs(Box, { gap: 1, children: [_jsx(Text, { dimColor: true, children: "error:" }), _jsx(Text, { color: "red", children: status.message })] }))] }), _jsxs(Box, { marginTop: 1, children: [status.state === "done" && (_jsxs(Text, { color: "green", bold: true, children: ["\u2713 Opened workspace in ", status.editor] })), status.state === "error" && (_jsxs(Text, { color: "red", bold: true, children: ["\u2717 ", status.message] }))] })] }));
|
|
72
|
+
}
|