pi-rakit-git 0.1.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 +36 -0
- package/extensions/index.js +109 -0
- package/package.json +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Pi Rakit Git
|
|
2
|
+
|
|
3
|
+
A focused Pi extension for inspecting Git state and committing changes that you explicitly staged.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js 20+
|
|
8
|
+
- Git
|
|
9
|
+
- Pi
|
|
10
|
+
- A Git repository
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pi install npm:pi-rakit-git
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or run `npx pi-rakit@latest --install` and select **Git**. Start or reload Pi after installation.
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
/git status
|
|
24
|
+
/git branch
|
|
25
|
+
/git commit Explain the staged change
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- `status` shows `git status --short --branch`.
|
|
29
|
+
- `branch` shows the current branch, or the short commit when HEAD is detached.
|
|
30
|
+
- `commit` asks for confirmation and runs `git commit -m` using the supplied message.
|
|
31
|
+
|
|
32
|
+
## Safety
|
|
33
|
+
|
|
34
|
+
`/git commit` never stages files. Review and stage exactly what you want with your normal workflow before invoking it. The command refuses to create a commit when the index is empty and does not bypass Git hooks.
|
|
35
|
+
|
|
36
|
+
The extension does not expose push, reset, clean, force, or branch-deletion operations.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
function defaultRunGit(args, cwd) {
|
|
4
|
+
return spawnSync("git", args, {
|
|
5
|
+
cwd,
|
|
6
|
+
encoding: "utf8",
|
|
7
|
+
shell: process.platform === "win32",
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function git(runGit, args, cwd, options = {}) {
|
|
12
|
+
const result = runGit(args, cwd);
|
|
13
|
+
if (result.error) throw result.error;
|
|
14
|
+
if (result.status !== 0 && !options.allowFailure) {
|
|
15
|
+
const message = String(result.stderr || result.stdout || "Git command failed").trim();
|
|
16
|
+
throw new Error(message);
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function repositoryRoot(runGit, cwd) {
|
|
22
|
+
return git(runGit, ["rev-parse", "--show-toplevel"], cwd).stdout.trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function gitStatus(options = {}) {
|
|
26
|
+
const cwd = options.cwd ?? process.cwd();
|
|
27
|
+
const runGit = options.runGit ?? defaultRunGit;
|
|
28
|
+
const root = repositoryRoot(runGit, cwd);
|
|
29
|
+
const output = git(runGit, ["status", "--short", "--branch"], root).stdout.trim();
|
|
30
|
+
return output || "Working tree clean.";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function currentBranch(options = {}) {
|
|
34
|
+
const cwd = options.cwd ?? process.cwd();
|
|
35
|
+
const runGit = options.runGit ?? defaultRunGit;
|
|
36
|
+
const root = repositoryRoot(runGit, cwd);
|
|
37
|
+
const branch = git(
|
|
38
|
+
runGit,
|
|
39
|
+
["symbolic-ref", "--quiet", "--short", "HEAD"],
|
|
40
|
+
root,
|
|
41
|
+
{ allowFailure: true },
|
|
42
|
+
);
|
|
43
|
+
if (branch.status === 0) return branch.stdout.trim();
|
|
44
|
+
const commit = git(runGit, ["rev-parse", "--short", "HEAD"], root).stdout.trim();
|
|
45
|
+
return `detached at ${commit}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function commitStaged(message, options = {}) {
|
|
49
|
+
const trimmedMessage = message?.trim();
|
|
50
|
+
if (!trimmedMessage) throw new Error("Commit message is required.");
|
|
51
|
+
|
|
52
|
+
const cwd = options.cwd ?? process.cwd();
|
|
53
|
+
const runGit = options.runGit ?? defaultRunGit;
|
|
54
|
+
const root = repositoryRoot(runGit, cwd);
|
|
55
|
+
const staged = git(runGit, ["diff", "--cached", "--quiet"], root, {
|
|
56
|
+
allowFailure: true,
|
|
57
|
+
});
|
|
58
|
+
if (staged.status === 0) throw new Error("No staged changes to commit.");
|
|
59
|
+
if (staged.status !== 1) {
|
|
60
|
+
const detail = String(staged.stderr || staged.stdout || "Unable to inspect staged changes").trim();
|
|
61
|
+
throw new Error(detail);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
git(runGit, ["commit", "-m", trimmedMessage], root);
|
|
65
|
+
return trimmedMessage;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function usage() {
|
|
69
|
+
return "Usage: /git status | branch | commit <message>";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default function gitExtension(pi) {
|
|
73
|
+
pi.registerCommand("git", {
|
|
74
|
+
description: "Inspect Git state or commit already-staged changes",
|
|
75
|
+
handler: async (args, ctx) => {
|
|
76
|
+
try {
|
|
77
|
+
const input = args.trim();
|
|
78
|
+
const [action, ...rest] = input.split(/\s+/).filter(Boolean);
|
|
79
|
+
if (!action) throw new Error(usage());
|
|
80
|
+
|
|
81
|
+
if (action === "status" && rest.length === 0) {
|
|
82
|
+
ctx.ui.notify(gitStatus(), "info");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (action === "branch" && rest.length === 0) {
|
|
86
|
+
ctx.ui.notify(currentBranch(), "info");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (action === "commit" && rest.length > 0) {
|
|
90
|
+
const message = rest.join(" ");
|
|
91
|
+
const choice = await ctx.ui.select(
|
|
92
|
+
`Commit staged changes with message “${message}”?`,
|
|
93
|
+
["Cancel", "Commit"],
|
|
94
|
+
);
|
|
95
|
+
if (choice !== "Commit") {
|
|
96
|
+
ctx.ui.notify("Cancelled.", "info");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
commitStaged(message);
|
|
100
|
+
ctx.ui.notify(`Committed staged changes: ${message}`, "info");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
throw new Error(usage());
|
|
104
|
+
} catch (error) {
|
|
105
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-rakit-git",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Focused and confirmation-gated Git commands for Pi",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"extensions",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"pi-package",
|
|
16
|
+
"pi-coding-agent",
|
|
17
|
+
"extension",
|
|
18
|
+
"git"
|
|
19
|
+
],
|
|
20
|
+
"pi": {
|
|
21
|
+
"extensions": [
|
|
22
|
+
"./extensions"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20"
|
|
27
|
+
}
|
|
28
|
+
}
|