@zigai/pi-mode 0.4.0 → 0.4.1
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 +3 -42
- package/package.json +3 -8
- package/src/index.ts +1 -49
- package/config.schema.json +0 -29
- package/src/constants.ts +0 -24
- package/src/editor.ts +0 -101
- package/src/mode-state.ts +0 -1193
- package/src/settings.ts +0 -439
- package/src/status.ts +0 -116
- package/src/storage.ts +0 -207
- package/src/types.ts +0 -55
package/src/storage.ts
DELETED
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import { CONFIG_DIR_NAME, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
export function getGlobalAgentDir(): string {
|
|
6
|
-
return getAgentDir();
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const EXTENSION_ID = "pi-mode";
|
|
10
|
-
const CONFIG_FILE = "config.json";
|
|
11
|
-
const SCHEMA_FILE = "config.schema.json";
|
|
12
|
-
|
|
13
|
-
const DEFAULT_MODES_CONFIG_FILE = {
|
|
14
|
-
$schema: `./${SCHEMA_FILE}`,
|
|
15
|
-
version: 1,
|
|
16
|
-
currentMode: "default",
|
|
17
|
-
modeShowName: false,
|
|
18
|
-
modeUseThinkingBorderColors: false,
|
|
19
|
-
modeShowThinkingLevelStatus: false,
|
|
20
|
-
modes: {},
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export function getGlobalModesPath(): string {
|
|
24
|
-
return path.join(getGlobalAgentDir(), EXTENSION_ID, CONFIG_FILE);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function getProjectModesPath(cwd: string): string {
|
|
28
|
-
return path.join(cwd, CONFIG_DIR_NAME, EXTENSION_ID, CONFIG_FILE);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function fileExists(filePath: string): Promise<boolean> {
|
|
32
|
-
try {
|
|
33
|
-
await fs.stat(filePath);
|
|
34
|
-
return true;
|
|
35
|
-
} catch {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export async function ensureDirForFile(filePath: string): Promise<void> {
|
|
41
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function getSchemaPath(configPath: string): string {
|
|
45
|
-
return path.join(path.dirname(configPath), SCHEMA_FILE);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function writeIfMissing(filePath: string, content: string): Promise<void> {
|
|
49
|
-
try {
|
|
50
|
-
await ensureDirForFile(filePath);
|
|
51
|
-
await fs.writeFile(filePath, content, { encoding: "utf8", flag: "wx" });
|
|
52
|
-
} catch (error: unknown) {
|
|
53
|
-
if (error instanceof Error && (error as NodeJS.ErrnoException).code === "EEXIST") return;
|
|
54
|
-
if (error instanceof Error) throw error;
|
|
55
|
-
throw new Error(String(error));
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function refreshSchemaFile(filePath: string, content: string): Promise<void> {
|
|
60
|
-
let temporaryPath: string | undefined;
|
|
61
|
-
try {
|
|
62
|
-
await ensureDirForFile(filePath);
|
|
63
|
-
try {
|
|
64
|
-
if ((await fs.readFile(filePath, "utf8")) === content) return;
|
|
65
|
-
} catch (error: unknown) {
|
|
66
|
-
if (!(error instanceof Error) || (error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
67
|
-
throw error;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const nextTemporaryPath = `${filePath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
72
|
-
await fs.writeFile(nextTemporaryPath, content, { encoding: "utf8", flag: "wx" });
|
|
73
|
-
temporaryPath = nextTemporaryPath;
|
|
74
|
-
await fs.rename(temporaryPath, filePath);
|
|
75
|
-
temporaryPath = undefined;
|
|
76
|
-
} catch (error: unknown) {
|
|
77
|
-
if (temporaryPath !== undefined) {
|
|
78
|
-
try {
|
|
79
|
-
await fs.unlink(temporaryPath);
|
|
80
|
-
} catch {
|
|
81
|
-
// Ignore cleanup failure while reporting the original scaffold failure.
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (error instanceof Error) throw error;
|
|
85
|
-
throw new Error(String(error));
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async function readBundledSchema(): Promise<string | undefined> {
|
|
90
|
-
try {
|
|
91
|
-
return await fs.readFile(new URL("../config.schema.json", import.meta.url), "utf8");
|
|
92
|
-
} catch {
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export async function scaffoldGlobalModesConfig(): Promise<void> {
|
|
98
|
-
const globalConfigPath = getGlobalModesPath();
|
|
99
|
-
const schema = await readBundledSchema();
|
|
100
|
-
if (schema !== undefined) {
|
|
101
|
-
await refreshSchemaFile(getSchemaPath(globalConfigPath), schema);
|
|
102
|
-
}
|
|
103
|
-
await writeIfMissing(
|
|
104
|
-
globalConfigPath,
|
|
105
|
-
`${JSON.stringify(DEFAULT_MODES_CONFIG_FILE, null, 2)}\n`,
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export async function getMtimeMs(filePath: string): Promise<number | null> {
|
|
110
|
-
try {
|
|
111
|
-
const stat = await fs.stat(filePath);
|
|
112
|
-
return stat.mtimeMs;
|
|
113
|
-
} catch {
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function sleep(ms: number): Promise<void> {
|
|
119
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function getErrorCode(error: unknown): string | undefined {
|
|
123
|
-
if (!(error instanceof Error)) return undefined;
|
|
124
|
-
const code = (error as NodeJS.ErrnoException).code;
|
|
125
|
-
if (typeof code === "string") return code;
|
|
126
|
-
return undefined;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function throwError(error: unknown): never {
|
|
130
|
-
if (error instanceof Error) throw error;
|
|
131
|
-
throw new Error(String(error));
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function getLockPathForFile(filePath: string): string {
|
|
135
|
-
return `${filePath}.lock`;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export async function withFileLock<T>(filePath: string, fn: () => Promise<T>): Promise<T> {
|
|
139
|
-
const lockPath = getLockPathForFile(filePath);
|
|
140
|
-
await ensureDirForFile(lockPath);
|
|
141
|
-
|
|
142
|
-
const start = Date.now();
|
|
143
|
-
while (true) {
|
|
144
|
-
try {
|
|
145
|
-
const handle = await fs.open(lockPath, "wx");
|
|
146
|
-
try {
|
|
147
|
-
await handle.writeFile(
|
|
148
|
-
JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() }) +
|
|
149
|
-
"\n",
|
|
150
|
-
"utf8",
|
|
151
|
-
);
|
|
152
|
-
} catch {
|
|
153
|
-
// ignore best-effort lock metadata
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
return await fn();
|
|
158
|
-
} finally {
|
|
159
|
-
await handle.close().catch(() => {});
|
|
160
|
-
await fs.unlink(lockPath).catch(() => {});
|
|
161
|
-
}
|
|
162
|
-
} catch (error: unknown) {
|
|
163
|
-
if (getErrorCode(error) !== "EEXIST") throwError(error);
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
const stat = await fs.stat(lockPath);
|
|
167
|
-
if (Date.now() - stat.mtimeMs > 30_000) {
|
|
168
|
-
await fs.unlink(lockPath);
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
} catch {
|
|
172
|
-
// ignore stale-lock checks
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (Date.now() - start > 5_000) {
|
|
176
|
-
throw new Error(`Timed out waiting for lock: ${lockPath}`);
|
|
177
|
-
}
|
|
178
|
-
await sleep(40 + Math.random() * 80);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export async function atomicWriteUtf8(filePath: string, content: string): Promise<void> {
|
|
184
|
-
await ensureDirForFile(filePath);
|
|
185
|
-
|
|
186
|
-
const dir = path.dirname(filePath);
|
|
187
|
-
const base = path.basename(filePath);
|
|
188
|
-
const tempPath = path.join(
|
|
189
|
-
dir,
|
|
190
|
-
`.${base}.tmp.${process.pid}.${Math.random().toString(16).slice(2)}`,
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
await fs.writeFile(tempPath, content, "utf8");
|
|
194
|
-
|
|
195
|
-
try {
|
|
196
|
-
await fs.rename(tempPath, filePath);
|
|
197
|
-
} catch (error: unknown) {
|
|
198
|
-
const code = getErrorCode(error);
|
|
199
|
-
if (code === "EEXIST" || code === "EPERM") {
|
|
200
|
-
await fs.unlink(filePath).catch(() => {});
|
|
201
|
-
await fs.rename(tempPath, filePath);
|
|
202
|
-
} else {
|
|
203
|
-
await fs.unlink(tempPath).catch(() => {});
|
|
204
|
-
throwError(error);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
2
|
-
import type { ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
|
|
4
|
-
export type ModeName = string;
|
|
5
|
-
|
|
6
|
-
export type ModeSpec = {
|
|
7
|
-
provider?: string;
|
|
8
|
-
modelId?: string;
|
|
9
|
-
thinkingLevel?: ThinkingLevel;
|
|
10
|
-
/**
|
|
11
|
-
* Optional theme color token to use for the editor border.
|
|
12
|
-
* If unset, the default editor border is used unless thinking-derived
|
|
13
|
-
* border colors are enabled in settings.
|
|
14
|
-
*/
|
|
15
|
-
color?: ThemeColor;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export type ModesFile = {
|
|
19
|
-
version: 1;
|
|
20
|
-
currentMode: ModeName;
|
|
21
|
-
modes: Record<ModeName, ModeSpec>;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export type ModeSpecPatch = {
|
|
25
|
-
provider?: string | null;
|
|
26
|
-
modelId?: string | null;
|
|
27
|
-
thinkingLevel?: ThinkingLevel | null;
|
|
28
|
-
color?: ThemeColor | null;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export type ModesPatch = {
|
|
32
|
-
currentMode?: ModeName;
|
|
33
|
-
modes?: Record<ModeName, ModeSpecPatch | null>;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export type ModeRuntime = {
|
|
37
|
-
filePath: string;
|
|
38
|
-
fileMtimeMs: number | null;
|
|
39
|
-
/**
|
|
40
|
-
* Snapshot of what we last loaded or synced from disk. Used to compute patches
|
|
41
|
-
* so multiple running pi processes do not clobber each other's edits.
|
|
42
|
-
*/
|
|
43
|
-
baseline: ModesFile | null;
|
|
44
|
-
data: ModesFile;
|
|
45
|
-
/**
|
|
46
|
-
* Last non-overlay mode. Used as cycle base while in the overlay custom mode.
|
|
47
|
-
*/
|
|
48
|
-
lastRealMode: string;
|
|
49
|
-
/**
|
|
50
|
-
* The effective current mode. Can temporarily be the overlay custom mode,
|
|
51
|
-
* which is not persisted and not selectable via /mode.
|
|
52
|
-
*/
|
|
53
|
-
currentMode: string;
|
|
54
|
-
applying: boolean;
|
|
55
|
-
};
|