@quandev104/pi-style 0.1.4 → 0.1.6
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/CHANGELOG.md +30 -0
- package/README.md +10 -6
- package/dist/extensions/pi-style.js +3939 -1431
- package/dist/extensions/pi-style.js.map +1 -1
- package/extension-src/pi-style/app/command-service.ts +2 -0
- package/extension-src/pi-style/domain/config-authorization.ts +6 -3
- package/extension-src/pi-style/domain/config-normalization.ts +21 -5
- package/extension-src/pi-style/domain/config-presets.ts +1 -1
- package/extension-src/pi-style/domain/config-types.ts +7 -3
- package/extension-src/pi-style/domain/theme.ts +6 -1
- package/extension-src/pi-style/features/editor/index.ts +169 -27
- package/extension-src/pi-style/features/messages/index.ts +66 -0
- package/extension-src/pi-style/features/tools/bash-execution.ts +112 -0
- package/extension-src/pi-style/features/tools/boxed/bash.ts +154 -130
- package/extension-src/pi-style/features/tools/boxed/batch.ts +50 -27
- package/extension-src/pi-style/features/tools/boxed/command-shape.ts +136 -0
- package/extension-src/pi-style/features/tools/boxed/find.ts +2 -2
- package/extension-src/pi-style/features/tools/boxed/gh.ts +1012 -0
- package/extension-src/pi-style/features/tools/boxed/git.ts +1960 -0
- package/extension-src/pi-style/features/tools/boxed/grep.ts +2 -2
- package/extension-src/pi-style/features/tools/boxed/output-tree.ts +9 -10
- package/extension-src/pi-style/features/tools/boxed/read.ts +3 -3
- package/extension-src/pi-style/features/tools/boxed/write.ts +2 -1
- package/extension-src/pi-style/pi/compatibility-coordinator.ts +32 -11
- package/extension-src/pi-style/pi/compatibility-probe.ts +341 -205
- package/extension-src/pi-style/pi/compatibility-registry.ts +19 -3
- package/extension-src/pi-style/pi/index.ts +21 -3
- package/extension-src/pi-style/pi/session-coordinator.ts +24 -0
- package/extension-src/pi-style/shared/box.ts +8 -4
- package/extension-src/pi-style/shared/split-diff.ts +9 -9
- package/package.json +9 -9
- package/themes/titanium-light.json +82 -0
- package/themes/titanium.json +79 -0
- package/themes/.gitkeep +0 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// Simple bash command shape detection, shared by the bash tool renderer and
|
|
2
|
+
// the git/gh semantic classifiers.
|
|
3
|
+
//
|
|
4
|
+
// A command is "simple" when pi-style can reason about it purely from its
|
|
5
|
+
// token list: single line, no shell metacharacters (pipes, redirects,
|
|
6
|
+
// substitutions), and no `&&`/`;`/`&` outside a leading `cd X &&` chain.
|
|
7
|
+
// Anything ambiguous returns null so the boxed command/response shell stays
|
|
8
|
+
// the fallback (ADR 0005 — no approximate rendering).
|
|
9
|
+
|
|
10
|
+
/** Tokens of a classifiable command after env/prefix/cd-chain stripping. */
|
|
11
|
+
export interface SimpleBashCommandShape {
|
|
12
|
+
/** Tokens after leading env assignments, prefix commands, and `cd X &&` chains. */
|
|
13
|
+
readonly tokens: string[];
|
|
14
|
+
/** Last directory from a leading `cd <dir> &&` / `cd <dir>;` chain. */
|
|
15
|
+
readonly cdDir?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const BASH_PREFIX_COMMANDS = new Set(["sudo", "env", "time", "nice", "nohup", "command", "stdbuf", "ionice", "watch"]);
|
|
19
|
+
// Pipes (`|`), `;`, and `&` are excluded here: the classifier validates them
|
|
20
|
+
// explicitly (allowing `cd X && cmd` chains and a trailing `| head/tail`).
|
|
21
|
+
const BASH_SHELL_META_CHARS = new Set(["<", ">", "(", ")", "`"]);
|
|
22
|
+
|
|
23
|
+
/** Tokenize a single command line, stripping quotes. Returns null on an
|
|
24
|
+
* unterminated quote. `hasMeta` is true if any shell metacharacter appears
|
|
25
|
+
* *outside* quotes (so `grep 'a|b' f` stays classifiable). */
|
|
26
|
+
function tokenizeCommandLine(line: string): { tokens: string[]; hasMeta: boolean } | null {
|
|
27
|
+
const tokens: string[] = [];
|
|
28
|
+
let current = "";
|
|
29
|
+
let inToken = false;
|
|
30
|
+
let quote: string | null = null;
|
|
31
|
+
let hasMeta = false;
|
|
32
|
+
for (let i = 0; i < line.length; i++) {
|
|
33
|
+
const char = line[i] ?? "";
|
|
34
|
+
if (quote) {
|
|
35
|
+
if (char === "\\" && quote === '"') {
|
|
36
|
+
current += line[++i] ?? "";
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (char === quote) {
|
|
40
|
+
quote = null;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
current += char;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (char === '"' || char === "'") {
|
|
47
|
+
quote = char;
|
|
48
|
+
inToken = true;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (char === " " || char === "\t") {
|
|
52
|
+
if (inToken) {
|
|
53
|
+
tokens.push(current);
|
|
54
|
+
current = "";
|
|
55
|
+
inToken = false;
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (BASH_SHELL_META_CHARS.has(char) || (char === "$" && (line[i + 1] ?? "") === "(")) {
|
|
60
|
+
hasMeta = true;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
current += char;
|
|
64
|
+
inToken = true;
|
|
65
|
+
}
|
|
66
|
+
if (quote) return null;
|
|
67
|
+
if (inToken) tokens.push(current);
|
|
68
|
+
return { tokens, hasMeta };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** `head [-n N]` / `tail [-n N]` truncation pipe tail (allowed at the end). */
|
|
72
|
+
function isHeadOrTailTail(tokens: readonly string[]): boolean {
|
|
73
|
+
if (tokens.length === 0 || (tokens[0] !== "head" && tokens[0] !== "tail")) return false;
|
|
74
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
75
|
+
const token = tokens[i] ?? "";
|
|
76
|
+
if (token === "-n") continue;
|
|
77
|
+
if (/^\d+$/.test(token)) continue;
|
|
78
|
+
if (/^-\d+$/.test(token)) continue;
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Tokenize a single-line bash command and verify it is simple enough to
|
|
86
|
+
* classify: no shell metacharacters (`<`, `>`, `(`, `)`, backtick, `$(`), no
|
|
87
|
+
* `&&`/`;`/`&` outside a leading `cd X &&` chain, and — unless
|
|
88
|
+
* `allowTrailingTruncationPipe` — no pipes at all. Returns null for anything
|
|
89
|
+
* ambiguous so callers fall back to the boxed shell. Newlines and unterminated
|
|
90
|
+
* quotes are rejected.
|
|
91
|
+
*/
|
|
92
|
+
export function parseSimpleBashCommand(
|
|
93
|
+
command: string,
|
|
94
|
+
options: { allowTrailingTruncationPipe?: boolean } = {},
|
|
95
|
+
): SimpleBashCommandShape | null {
|
|
96
|
+
const commandText = String(command ?? "").trim();
|
|
97
|
+
if (!commandText || commandText.includes("\n")) return null;
|
|
98
|
+
const tokenized = tokenizeCommandLine(commandText);
|
|
99
|
+
if (!tokenized || tokenized.hasMeta || tokenized.tokens.length === 0) return null;
|
|
100
|
+
let tokens = tokenized.tokens;
|
|
101
|
+
|
|
102
|
+
if (options.allowTrailingTruncationPipe) {
|
|
103
|
+
// Allow a single trailing truncation pipe: `cmd | head [-n] N` / `| tail …`.
|
|
104
|
+
const pipes = tokens.flatMap((token, i) => (token === "|" ? [i] : []));
|
|
105
|
+
if (pipes.length > 0) {
|
|
106
|
+
if (pipes.length > 1) return null;
|
|
107
|
+
const last = pipes[0] ?? -1;
|
|
108
|
+
if (!isHeadOrTailTail(tokens.slice(last + 1))) return null;
|
|
109
|
+
tokens = tokens.slice(0, last);
|
|
110
|
+
}
|
|
111
|
+
} else if (tokens.includes("|")) {
|
|
112
|
+
// git/gh classification keeps the pipe rule strict (ADR 0005): any pipe
|
|
113
|
+
// falls back to the raw boxed shell.
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let index = 0;
|
|
118
|
+
// Skip leading environment assignments (FOO=bar ...) and prefix commands.
|
|
119
|
+
while (index < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[index] ?? "")) index++;
|
|
120
|
+
while (index < tokens.length && BASH_PREFIX_COMMANDS.has(tokens[index] ?? "")) index++;
|
|
121
|
+
// `cd <dir> &&` / `cd <dir>;` chains: the last directory becomes the default
|
|
122
|
+
// path when the command itself carries none.
|
|
123
|
+
let cdDir: string | undefined;
|
|
124
|
+
while (
|
|
125
|
+
tokens[index] === "cd" &&
|
|
126
|
+
index + 2 < tokens.length &&
|
|
127
|
+
tokens[index + 1] !== undefined &&
|
|
128
|
+
(tokens[index + 2] === "&&" || tokens[index + 2] === ";")
|
|
129
|
+
) {
|
|
130
|
+
cdDir = tokens[index + 1];
|
|
131
|
+
index += 3;
|
|
132
|
+
}
|
|
133
|
+
const rest = tokens.slice(index);
|
|
134
|
+
if (rest.length === 0 || rest.some((token) => token === "&&" || token === ";" || token === "&")) return null;
|
|
135
|
+
return { tokens: rest, ...(cdDir !== undefined ? { cdDir } : {}) };
|
|
136
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Boxed find tool renderer.
|
|
2
2
|
//
|
|
3
3
|
// find calls render as a boxless tree panel — a lone find shows its parsed
|
|
4
|
-
// output as a flat `
|
|
4
|
+
// output as a flat `Find: <pattern> <N> files · in <path>` tree; consecutive
|
|
5
5
|
// find calls group into one panel with per-member nested subtrees (see
|
|
6
6
|
// batch.ts). Pending/failed calls without output fall back to a path row.
|
|
7
7
|
|
|
@@ -21,7 +21,7 @@ import { type BoxedToolDefinition, noteExecutionStart } from "./shared.js";
|
|
|
21
21
|
const FIND_META: BatchToolMeta = Object.freeze({
|
|
22
22
|
toolName: "find",
|
|
23
23
|
label: "Find",
|
|
24
|
-
headerLabel: "
|
|
24
|
+
headerLabel: "Find",
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
function pathLabel(rawPath: string): string {
|