@gogomi/pi-windows-shell 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/LICENSE +21 -0
- package/README.md +77 -0
- package/index.ts +1050 -0
- package/output.ts +139 -0
- package/package.json +48 -0
- package/paths.ts +66 -0
- package/process-registry.ts +210 -0
- package/prompts/windows-shell-policy.md +113 -0
- package/shell.ts +304 -0
- package/tsconfig.json +15 -0
- package/types.ts +52 -0
package/output.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output truncation and tail reading utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import { createReadStream } from "node:fs";
|
|
7
|
+
import type { TailResult } from "./types.js";
|
|
8
|
+
|
|
9
|
+
const DEFAULT_MAX_BYTES = 50000;
|
|
10
|
+
const DEFAULT_TAIL_LINES = 100;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Read lines from the end of a file (tail functionality).
|
|
14
|
+
*/
|
|
15
|
+
export async function tailFile(
|
|
16
|
+
filePath: string,
|
|
17
|
+
options: {
|
|
18
|
+
lines?: number;
|
|
19
|
+
maxBytes?: number;
|
|
20
|
+
} = {}
|
|
21
|
+
): Promise<TailResult> {
|
|
22
|
+
const lines = options.lines ?? DEFAULT_TAIL_LINES;
|
|
23
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
await fs.promises.access(filePath);
|
|
27
|
+
} catch {
|
|
28
|
+
return {
|
|
29
|
+
lines: [],
|
|
30
|
+
truncated: false,
|
|
31
|
+
totalLines: 0,
|
|
32
|
+
linesRead: 0,
|
|
33
|
+
fileExists: false,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const stats = await fs.promises.stat(filePath);
|
|
39
|
+
const fileSize = stats.size;
|
|
40
|
+
|
|
41
|
+
// If file is small enough, read it all
|
|
42
|
+
if (fileSize <= maxBytes) {
|
|
43
|
+
const content = await fs.promises.readFile(filePath, "utf-8");
|
|
44
|
+
const allLines = content.split(/\r?\n/).filter((l) => l.length > 0);
|
|
45
|
+
const tailLines = allLines.slice(-lines);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
lines: tailLines,
|
|
49
|
+
truncated: allLines.length > tailLines.length,
|
|
50
|
+
totalLines: allLines.length,
|
|
51
|
+
linesRead: tailLines.length,
|
|
52
|
+
fileExists: true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// For large files, stream from the end
|
|
57
|
+
const buffer = Buffer.alloc(maxBytes);
|
|
58
|
+
const fd = await fs.promises.open(filePath, "r");
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// Read from near the end
|
|
62
|
+
const startPos = Math.max(0, fileSize - maxBytes);
|
|
63
|
+
const { bytesRead } = await fd.read(buffer, 0, maxBytes, startPos);
|
|
64
|
+
const content = buffer.toString("utf-8", 0, bytesRead);
|
|
65
|
+
|
|
66
|
+
// Split and find tail lines
|
|
67
|
+
const allLines = content.split(/\r?\n/);
|
|
68
|
+
|
|
69
|
+
// Skip partial first line if we started mid-line
|
|
70
|
+
const skipPartial = startPos > 0 && !content.startsWith("\n") && !content.startsWith("\r");
|
|
71
|
+
const lineStart = skipPartial ? 1 : 0;
|
|
72
|
+
|
|
73
|
+
const totalLines = allLines.length - lineStart;
|
|
74
|
+
const tailStartIndex = Math.max(lineStart, allLines.length - lines);
|
|
75
|
+
const tailLines = allLines.slice(tailStartIndex);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
lines: tailLines,
|
|
79
|
+
truncated: tailStartIndex > lineStart,
|
|
80
|
+
totalLines,
|
|
81
|
+
linesRead: tailLines.length,
|
|
82
|
+
fileExists: true,
|
|
83
|
+
};
|
|
84
|
+
} finally {
|
|
85
|
+
await fd.close();
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Failed to read output file: ${filePath}. Error: ${error instanceof Error ? error.message : String(error)}`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Truncate output text to a maximum size.
|
|
96
|
+
*/
|
|
97
|
+
export function truncateOutput(
|
|
98
|
+
text: string,
|
|
99
|
+
options: {
|
|
100
|
+
maxBytes?: number;
|
|
101
|
+
maxLines?: number;
|
|
102
|
+
} = {}
|
|
103
|
+
): { text: string; truncated: boolean } {
|
|
104
|
+
const maxBytes = options.maxBytes ?? 50000;
|
|
105
|
+
const maxLines = options.maxLines ?? 2000;
|
|
106
|
+
|
|
107
|
+
// Check line limit
|
|
108
|
+
const lines = text.split(/\r?\n/);
|
|
109
|
+
if (lines.length > maxLines) {
|
|
110
|
+
const truncatedLines = lines.slice(0, maxLines);
|
|
111
|
+
return {
|
|
112
|
+
text: truncatedLines.join("\n") + `\n[output truncated: showing first ${maxLines} lines of ${lines.length}]`,
|
|
113
|
+
truncated: true,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Check byte limit
|
|
118
|
+
if (Buffer.byteLength(text, "utf-8") > maxBytes) {
|
|
119
|
+
let truncatedText = "";
|
|
120
|
+
let currentBytes = 0;
|
|
121
|
+
|
|
122
|
+
for (const line of lines) {
|
|
123
|
+
const lineBytes = Buffer.byteLength(line + "\n", "utf-8");
|
|
124
|
+
if (currentBytes + lineBytes > maxBytes) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
truncatedText += line + "\n";
|
|
128
|
+
currentBytes += lineBytes;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
text: truncatedText.trimEnd() + `\n[output truncated: ${Buffer.byteLength(text, "utf-8")} bytes down to ${maxBytes} bytes]`,
|
|
133
|
+
truncated: true,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { text, truncated: false };
|
|
138
|
+
}
|
|
139
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gogomi/pi-windows-shell",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Windows PowerShell and process-management tools for Pi coding agent.",
|
|
6
|
+
"main": "index.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"index.ts",
|
|
9
|
+
"shell.ts",
|
|
10
|
+
"output.ts",
|
|
11
|
+
"paths.ts",
|
|
12
|
+
"process-registry.ts",
|
|
13
|
+
"types.ts",
|
|
14
|
+
"prompts/windows-shell-policy.md",
|
|
15
|
+
"tsconfig.json",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"pi-package",
|
|
21
|
+
"pi-extension",
|
|
22
|
+
"powershell",
|
|
23
|
+
"windows",
|
|
24
|
+
"process-management"
|
|
25
|
+
],
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/Gogomy/pi-windows-shell.git"
|
|
29
|
+
},
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"pi": {
|
|
32
|
+
"extensions": [
|
|
33
|
+
"./index.ts"
|
|
34
|
+
],
|
|
35
|
+
"prompts": [
|
|
36
|
+
"./prompts"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@sinclair/typebox": "^0.34.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"typescript": "^5.0.0"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/paths.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path helpers for pi-windows-shell
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get the base directory for pi-windows-shell data.
|
|
10
|
+
* Uses LOCALAPPDATA, falls back to TEMP.
|
|
11
|
+
*/
|
|
12
|
+
export function getBaseDir(): string {
|
|
13
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
14
|
+
if (localAppData) {
|
|
15
|
+
return path.join(localAppData, "pi-windows-shell");
|
|
16
|
+
}
|
|
17
|
+
return path.join(os.tmpdir(), "pi-windows-shell");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get the registry file path.
|
|
22
|
+
*/
|
|
23
|
+
export function getRegistryPath(): string {
|
|
24
|
+
return path.join(getBaseDir(), "processes.json");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the logs directory path.
|
|
29
|
+
*/
|
|
30
|
+
export function getLogsDir(): string {
|
|
31
|
+
return path.join(getBaseDir(), "logs");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generate a default output file path for a new process.
|
|
36
|
+
*/
|
|
37
|
+
export function getDefaultOutputFile(name: string): string {
|
|
38
|
+
const timestamp = Date.now().toString(36);
|
|
39
|
+
const safeName = (name || "process")
|
|
40
|
+
.replace(/[^a-zA-Z0-9_-]/g, "_")
|
|
41
|
+
.replace(/_{2,}/g, "_")
|
|
42
|
+
.slice(0, 50);
|
|
43
|
+
|
|
44
|
+
const logsDir = getLogsDir();
|
|
45
|
+
return path.join(logsDir, `${timestamp}-${safeName}.log`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Ensure a directory exists, creating it if necessary.
|
|
50
|
+
*/
|
|
51
|
+
export async function ensureDir(dirPath: string): Promise<void> {
|
|
52
|
+
const { mkdir } = await import("node:fs/promises");
|
|
53
|
+
await mkdir(dirPath, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Generate a unique process ID.
|
|
58
|
+
*/
|
|
59
|
+
export function generateProcessId(name: string): string {
|
|
60
|
+
const timestamp = Date.now().toString(36);
|
|
61
|
+
const safeName = (name || "process")
|
|
62
|
+
.replace(/[^a-zA-Z0-9_-]/g, "_")
|
|
63
|
+
.slice(0, 20);
|
|
64
|
+
return `${timestamp}-${safeName}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent process registry for pi-windows-shell
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import type { ProcessRegistry, ManagedProcess } from "./types.js";
|
|
7
|
+
import { getRegistryPath, ensureDir, getBaseDir } from "./paths.js";
|
|
8
|
+
|
|
9
|
+
const REGISTRY_VERSION = "1.0.0";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Ensure the base directory exists.
|
|
13
|
+
*/
|
|
14
|
+
async function ensureBaseDir(): Promise<void> {
|
|
15
|
+
await ensureDir(getBaseDir());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Load the process registry from disk.
|
|
20
|
+
*/
|
|
21
|
+
export async function loadRegistry(): Promise<ProcessRegistry> {
|
|
22
|
+
try {
|
|
23
|
+
await ensureBaseDir();
|
|
24
|
+
const path = getRegistryPath();
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
await fs.promises.access(path);
|
|
28
|
+
} catch {
|
|
29
|
+
// File doesn't exist, return empty registry
|
|
30
|
+
return { version: REGISTRY_VERSION, processes: [] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const content = await fs.promises.readFile(path, "utf-8");
|
|
34
|
+
const data = JSON.parse(content) as ProcessRegistry;
|
|
35
|
+
|
|
36
|
+
// Validate version
|
|
37
|
+
if (!data.version || !Array.isArray(data.processes)) {
|
|
38
|
+
return { version: REGISTRY_VERSION, processes: [] };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return data;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(`Failed to load registry: ${error}`);
|
|
44
|
+
return { version: REGISTRY_VERSION, processes: [] };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Save the process registry to disk.
|
|
50
|
+
*/
|
|
51
|
+
export async function saveRegistry(registry: ProcessRegistry): Promise<void> {
|
|
52
|
+
try {
|
|
53
|
+
await ensureBaseDir();
|
|
54
|
+
const path = getRegistryPath();
|
|
55
|
+
await fs.promises.writeFile(path, JSON.stringify(registry, null, 2), "utf-8");
|
|
56
|
+
} catch (error) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Failed to save registry: ${error instanceof Error ? error.message : String(error)}`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Add a new process to the registry.
|
|
65
|
+
*/
|
|
66
|
+
export async function addProcess(process: ManagedProcess): Promise<void> {
|
|
67
|
+
const registry = await loadRegistry();
|
|
68
|
+
registry.processes.push(process);
|
|
69
|
+
await saveRegistry(registry);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Update a process in the registry.
|
|
74
|
+
*/
|
|
75
|
+
export async function updateProcess(id: string, updates: Partial<ManagedProcess>): Promise<boolean> {
|
|
76
|
+
const registry = await loadRegistry();
|
|
77
|
+
const index = registry.processes.findIndex((p) => p.id === id);
|
|
78
|
+
|
|
79
|
+
if (index === -1) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
registry.processes[index] = { ...registry.processes[index], ...updates };
|
|
84
|
+
await saveRegistry(registry);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Remove a process from the registry.
|
|
90
|
+
*/
|
|
91
|
+
export async function removeProcess(id: string): Promise<boolean> {
|
|
92
|
+
const registry = await loadRegistry();
|
|
93
|
+
const initialLength = registry.processes.length;
|
|
94
|
+
registry.processes = registry.processes.filter((p) => p.id !== id);
|
|
95
|
+
|
|
96
|
+
if (registry.processes.length === initialLength) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await saveRegistry(registry);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get a process by ID.
|
|
106
|
+
*/
|
|
107
|
+
export async function getProcess(id: string): Promise<ManagedProcess | null> {
|
|
108
|
+
const registry = await loadRegistry();
|
|
109
|
+
return registry.processes.find((p) => p.id === id) ?? null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get all processes.
|
|
114
|
+
*/
|
|
115
|
+
export async function getAllProcesses(): Promise<ManagedProcess[]> {
|
|
116
|
+
const registry = await loadRegistry();
|
|
117
|
+
return registry.processes;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Remove multiple processes by IDs.
|
|
122
|
+
*/
|
|
123
|
+
export async function removeProcesses(ids: string[]): Promise<number> {
|
|
124
|
+
const registry = await loadRegistry();
|
|
125
|
+
const initialLength = registry.processes.length;
|
|
126
|
+
const idSet = new Set(ids);
|
|
127
|
+
registry.processes = registry.processes.filter((p) => !idSet.has(p.id));
|
|
128
|
+
|
|
129
|
+
const removed = initialLength - registry.processes.length;
|
|
130
|
+
if (removed > 0) {
|
|
131
|
+
await saveRegistry(registry);
|
|
132
|
+
}
|
|
133
|
+
return removed;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Clean up stale registry entries.
|
|
138
|
+
*/
|
|
139
|
+
export async function cleanupRegistry(options: {
|
|
140
|
+
removeExited?: boolean;
|
|
141
|
+
deleteLogs?: boolean;
|
|
142
|
+
olderThanDays?: number;
|
|
143
|
+
}): Promise<{
|
|
144
|
+
removedEntries: number;
|
|
145
|
+
deletedLogs: number;
|
|
146
|
+
keptRunning: number;
|
|
147
|
+
}> {
|
|
148
|
+
const registry = await loadRegistry();
|
|
149
|
+
const toRemove: string[] = [];
|
|
150
|
+
let deletedLogs = 0;
|
|
151
|
+
|
|
152
|
+
// Handle log deletion
|
|
153
|
+
if (options.deleteLogs && options.olderThanDays) {
|
|
154
|
+
const olderThan = Date.now() - options.olderThanDays * 24 * 60 * 60 * 1000;
|
|
155
|
+
|
|
156
|
+
for (const proc of registry.processes) {
|
|
157
|
+
try {
|
|
158
|
+
const stat = await fs.promises.stat(proc.outputFile);
|
|
159
|
+
if (stat.mtimeMs < olderThan) {
|
|
160
|
+
await fs.promises.unlink(proc.outputFile);
|
|
161
|
+
deletedLogs++;
|
|
162
|
+
}
|
|
163
|
+
} catch {
|
|
164
|
+
// File doesn't exist or can't be accessed, skip
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Handle registry entry removal
|
|
170
|
+
if (options.removeExited) {
|
|
171
|
+
for (const proc of registry.processes) {
|
|
172
|
+
if (proc.status === "exited") {
|
|
173
|
+
toRemove.push(proc.id);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const removedEntries = toRemove.length;
|
|
179
|
+
if (removedEntries > 0) {
|
|
180
|
+
await removeProcesses(toRemove);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Count kept running processes, excluding those just removed
|
|
184
|
+
const removedIds = new Set(toRemove);
|
|
185
|
+
const remaining = registry.processes.filter(
|
|
186
|
+
(p) => !removedIds.has(p.id) && p.status === "running"
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
removedEntries,
|
|
191
|
+
deletedLogs,
|
|
192
|
+
keptRunning: remaining.length,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Check if a process with given PID is still alive.
|
|
198
|
+
* Returns the process status or null if not found.
|
|
199
|
+
*/
|
|
200
|
+
export async function checkProcessAlive(pid: number): Promise<"running" | "exited" | "unknown"> {
|
|
201
|
+
try {
|
|
202
|
+
// Try using Node's process kill check
|
|
203
|
+
process.kill(pid, 0);
|
|
204
|
+
return "running";
|
|
205
|
+
} catch {
|
|
206
|
+
// ESRCH means process doesn't exist
|
|
207
|
+
// Other errors may mean permission issues
|
|
208
|
+
return "exited";
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Windows Shell Policy
|
|
2
|
+
|
|
3
|
+
The environment runs on Windows.
|
|
4
|
+
|
|
5
|
+
These rules are always active when choosing how to run commands.
|
|
6
|
+
|
|
7
|
+
## Shell/tool selection
|
|
8
|
+
|
|
9
|
+
Use Bash only for Git-oriented and Unix-like repository inspection workflows:
|
|
10
|
+
|
|
11
|
+
- `git status`
|
|
12
|
+
- `git diff`
|
|
13
|
+
- `git log`
|
|
14
|
+
- `git grep`
|
|
15
|
+
- patch inspection
|
|
16
|
+
- Unix-style text pipelines
|
|
17
|
+
- `grep`, `sed`, `awk`, `find`, `xargs`
|
|
18
|
+
|
|
19
|
+
Use PowerShell for Windows-native workflows:
|
|
20
|
+
|
|
21
|
+
- Windows paths such as `C:\...` or `D:\...`
|
|
22
|
+
- Windows environment variables such as `$env:USERPROFILE`
|
|
23
|
+
- `.exe`, `.cmd`, `.bat`, `.ps1`
|
|
24
|
+
- process management
|
|
25
|
+
- killing processes by PID or port
|
|
26
|
+
- checking installed commands with `Get-Command`
|
|
27
|
+
- Python virtual environments on Windows
|
|
28
|
+
- npm/yarn/pnpm/npx commands that resolve through `.cmd` launchers
|
|
29
|
+
- Windows services, registry, or system configuration
|
|
30
|
+
- launching Windows executables such as Godot, Blender, editors, or installers
|
|
31
|
+
|
|
32
|
+
Do not mix Bash syntax and PowerShell syntax in the same command.
|
|
33
|
+
|
|
34
|
+
Prefer `git diff` over shell-specific `diff` when inspecting repository changes.
|
|
35
|
+
|
|
36
|
+
## Windows path handling
|
|
37
|
+
|
|
38
|
+
Do not assume Windows paths are valid inside Bash.
|
|
39
|
+
|
|
40
|
+
If a command uses a Windows path such as:
|
|
41
|
+
|
|
42
|
+
```txt
|
|
43
|
+
C:\...
|
|
44
|
+
D:\...
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
use PowerShell.
|
|
48
|
+
|
|
49
|
+
Do not manually convert Windows paths to `/mnt/c/...`, `/c/...`, or `/d/...` unless the active Bash environment has already been verified to support that path style.
|
|
50
|
+
|
|
51
|
+
Do not assume WSL is available.
|
|
52
|
+
|
|
53
|
+
## Long-running commands
|
|
54
|
+
|
|
55
|
+
Do not use Bash background syntax on Windows:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm run dev &
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For long-running commands, background servers, file watchers, REPLs, or GUI applications, use the environment-provided Windows process tools when available:
|
|
62
|
+
|
|
63
|
+
- `win_start_process`
|
|
64
|
+
- `win_process_status`
|
|
65
|
+
- `win_read_output`
|
|
66
|
+
- `win_stop_process`
|
|
67
|
+
- `win_list_processes`
|
|
68
|
+
|
|
69
|
+
For stuck ports, use the environment-provided port tool when available:
|
|
70
|
+
|
|
71
|
+
- `win_kill_port`
|
|
72
|
+
|
|
73
|
+
Examples of long-running or blocking commands include:
|
|
74
|
+
|
|
75
|
+
- `npm run dev`
|
|
76
|
+
- `pnpm dev`
|
|
77
|
+
- `yarn dev`
|
|
78
|
+
- `godot . -e`
|
|
79
|
+
- local web servers
|
|
80
|
+
- file watchers
|
|
81
|
+
- REPLs
|
|
82
|
+
- GUI applications
|
|
83
|
+
|
|
84
|
+
## Command availability
|
|
85
|
+
|
|
86
|
+
Do not assume project-specific executable paths or tool names globally.
|
|
87
|
+
|
|
88
|
+
If a command is unavailable, diagnose it before retrying.
|
|
89
|
+
|
|
90
|
+
For PowerShell-native checks, use:
|
|
91
|
+
|
|
92
|
+
```powershell
|
|
93
|
+
Get-Command <command-name> -ErrorAction SilentlyContinue
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
If the environment provides `win_which`, it may also be used for Windows command resolution.
|
|
97
|
+
|
|
98
|
+
For Bash-only tools, use Bash diagnostics only after confirming the command is intended to run in Bash.
|
|
99
|
+
|
|
100
|
+
## Failure handling
|
|
101
|
+
|
|
102
|
+
When a command fails, do not retry blindly.
|
|
103
|
+
|
|
104
|
+
Identify:
|
|
105
|
+
|
|
106
|
+
1. Which shell/tool ran it.
|
|
107
|
+
2. The current working directory.
|
|
108
|
+
3. The exit code.
|
|
109
|
+
4. stderr/stdout.
|
|
110
|
+
5. Whether the command is Windows-native, Unix-like, or environment-provided.
|
|
111
|
+
6. Whether the path syntax matches the selected shell.
|
|
112
|
+
|
|
113
|
+
Then choose the corrected shell or tool before retrying.
|