@oh-my-pi/pi-utils 13.11.0 → 13.12.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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/dirs.ts +151 -67
  3. package/src/ptree.ts +1 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-utils",
4
- "version": "13.11.0",
4
+ "version": "13.12.0",
5
5
  "description": "Shared utilities for pi packages",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
package/src/dirs.ts CHANGED
@@ -3,6 +3,12 @@
3
3
  *
4
4
  * Uses PI_CONFIG_DIR (default ".omp") for the config root and
5
5
  * PI_CODING_AGENT_DIR to override the agent directory.
6
+ *
7
+ * On Linux, if XDG_DATA_HOME / XDG_STATE_HOME / XDG_CACHE_HOME environment
8
+ * variables are set, paths are redirected to XDG-compliant locations under
9
+ * $XDG_*_HOME/omp/. This requires running `omp config migrate` first to
10
+ * move data to the new locations. No filesystem existence checks are performed
11
+ * — if the env var is set, omp trusts that the migration has been done.
6
12
  */
7
13
 
8
14
  import * as fs from "node:fs";
@@ -23,7 +29,7 @@ export const VERSION: string = version;
23
29
  export const MIN_BUN_VERSION: string = engines.bun.replace(/[^0-9.]/g, "");
24
30
 
25
31
  // =============================================================================
26
- // Root directories
32
+ // Project directory
27
33
  // =============================================================================
28
34
 
29
35
  /**
@@ -64,23 +70,117 @@ export function getConfigAgentDirName(): string {
64
70
  return `${getConfigDirName()}/agent`;
65
71
  }
66
72
 
73
+ // =============================================================================
74
+ // DirResolver — cached, XDG-aware path resolution
75
+ // =============================================================================
76
+
77
+ type XdgCategory = "data" | "state" | "cache";
78
+
79
+ /**
80
+ * Resolves and caches all omp directory paths. On Linux, when XDG environment
81
+ * variables are set, paths are redirected under $XDG_*_HOME/omp/. A new
82
+ * instance is created whenever the agent directory changes, which naturally
83
+ * invalidates all cached paths.
84
+ */
85
+ class DirResolver {
86
+ readonly configRoot: string;
87
+ readonly agentDir: string;
88
+
89
+ // Per-category base dirs. Without XDG, all three equal configRoot / agentDir.
90
+ // With XDG on Linux, they point to $XDG_*_HOME/omp/.
91
+ readonly #rootDirs: Record<XdgCategory, string>;
92
+ readonly #agentDirs: Record<XdgCategory, string>;
93
+
94
+ readonly #rootCache = new Map<string, string>();
95
+ readonly #agentCache = new Map<string, string>();
96
+
97
+ constructor(agentDirOverride?: string) {
98
+ this.configRoot = path.join(os.homedir(), getConfigDirName());
99
+
100
+ const defaultAgent = path.join(this.configRoot, "agent");
101
+ this.agentDir = agentDirOverride ? path.resolve(agentDirOverride) : defaultAgent;
102
+ const isDefault = this.agentDir === defaultAgent;
103
+
104
+ // XDG is a Linux convention. On other platforms, or for non-default
105
+ // profiles, all categories resolve to the legacy paths.
106
+ let xdgData: string | undefined;
107
+ let xdgState: string | undefined;
108
+ let xdgCache: string | undefined;
109
+ if (process.platform === "linux" && isDefault) {
110
+ const resolveIf = (envVar: string) => {
111
+ const value = process.env[envVar];
112
+ if (value) {
113
+ try {
114
+ const joined = path.join(value, APP_NAME);
115
+ if (fs.existsSync(joined)) {
116
+ return joined;
117
+ }
118
+ } catch {}
119
+ }
120
+ return undefined;
121
+ };
122
+ xdgData = resolveIf("XDG_DATA_HOME");
123
+ xdgState = resolveIf("XDG_STATE_HOME");
124
+ xdgCache = resolveIf("XDG_CACHE_HOME");
125
+ }
126
+
127
+ this.#rootDirs = {
128
+ data: xdgData ?? this.configRoot,
129
+ state: xdgState ?? this.configRoot,
130
+ cache: xdgCache ?? this.configRoot,
131
+ };
132
+ // XDG flattens the agent/ prefix: ~/.omp/agent/sessions → $XDG_DATA_HOME/omp/sessions
133
+ this.#agentDirs = {
134
+ data: xdgData ?? this.agentDir,
135
+ state: xdgState ?? this.agentDir,
136
+ cache: xdgCache ?? this.agentDir,
137
+ };
138
+ }
139
+
140
+ /** Config-root subdirectory, with optional XDG override. */
141
+ rootSubdir(subdir: string, xdg?: XdgCategory): string {
142
+ const cached = this.#rootCache.get(subdir);
143
+ if (cached) return cached;
144
+ const base = xdg ? this.#rootDirs[xdg] : this.configRoot;
145
+ const result = path.join(base, subdir);
146
+ this.#rootCache.set(subdir, result);
147
+ return result;
148
+ }
149
+
150
+ /** Agent subdirectory, with optional XDG override. */
151
+ agentSubdir(userAgentDir: string | undefined, subdir: string, xdg?: XdgCategory): string {
152
+ if (!userAgentDir || userAgentDir === this.agentDir) {
153
+ const cached = this.#agentCache.get(subdir);
154
+ if (cached) return cached;
155
+ const base = xdg ? this.#agentDirs[xdg] : this.agentDir;
156
+ const result = path.join(base, subdir);
157
+ this.#agentCache.set(subdir, result);
158
+ return result;
159
+ }
160
+ return path.join(userAgentDir, subdir);
161
+ }
162
+ }
163
+
164
+ let dirs = new DirResolver(process.env.PI_CODING_AGENT_DIR);
165
+
166
+ // =============================================================================
167
+ // Root directories
168
+ // =============================================================================
169
+
67
170
  /** Get the config root directory (~/.omp). */
68
171
  export function getConfigRootDir(): string {
69
- return path.join(os.homedir(), getConfigDirName());
172
+ return dirs.configRoot;
70
173
  }
71
174
 
72
- let agentDir = process.env.PI_CODING_AGENT_DIR || path.join(getConfigRootDir(), "agent");
73
-
74
- /** Set the coding agent directory. */
175
+ /** Set the coding agent directory. Creates a fresh resolver, invalidating all cached paths. */
75
176
  export function setAgentDir(dir: string): void {
76
- agentDir = dir;
77
- agentCache.clear();
177
+ dirs = new DirResolver(dir);
78
178
  process.env.PI_CODING_AGENT_DIR = dir;
79
179
  }
80
180
 
81
181
  /** Get the agent config directory (~/.omp/agent). */
82
182
  export function getAgentDir(): string {
83
- return agentDir;
183
+ return dirs.agentDir;
84
184
  }
85
185
 
86
186
  /** Get the project-local config directory (.omp). */
@@ -88,49 +188,18 @@ export function getProjectAgentDir(cwd: string = getProjectDir()): string {
88
188
  return path.join(cwd, CONFIG_DIR_NAME);
89
189
  }
90
190
 
91
- // =============================================================================
92
- // Caching utilities
93
- // =============================================================================
94
-
95
- const rootCache = new Map<string, any>();
96
-
97
- function getRootSubdir(subdir: string): string {
98
- if (rootCache.has(subdir)) {
99
- return rootCache.get(subdir);
100
- }
101
- const result = path.join(getConfigRootDir(), subdir);
102
- rootCache.set(subdir, result);
103
- return result;
104
- }
105
-
106
- const agentCache = new Map<string, any>();
107
-
108
- function getAgentSubdir(userAgentDir: string | undefined, subdir: string): string {
109
- if (!userAgentDir || userAgentDir === agentDir) {
110
- if (agentCache.has(subdir)) {
111
- return agentCache.get(subdir);
112
- } else {
113
- const result = path.join(agentDir, subdir);
114
- agentCache.set(subdir, result);
115
- return result;
116
- }
117
- } else {
118
- return path.join(userAgentDir, subdir);
119
- }
120
- }
121
-
122
191
  // =============================================================================
123
192
  // Config-root subdirectories (~/.omp/*)
124
193
  // =============================================================================
125
194
 
126
195
  /** Get the reports directory (~/.omp/reports). */
127
196
  export function getReportsDir(): string {
128
- return getRootSubdir("reports");
197
+ return dirs.rootSubdir("reports", "state");
129
198
  }
130
199
 
131
200
  /** Get the logs directory (~/.omp/logs). */
132
201
  export function getLogsDir(): string {
133
- return getRootSubdir("logs");
202
+ return dirs.rootSubdir("logs", "state");
134
203
  }
135
204
 
136
205
  /** Get the path to a dated log file (~/.omp/logs/omp.YYYY-MM-DD.log). */
@@ -140,52 +209,52 @@ export function getLogPath(date = new Date()): string {
140
209
 
141
210
  /** Get the plugins directory (~/.omp/plugins). */
142
211
  export function getPluginsDir(): string {
143
- return getRootSubdir("plugins");
212
+ return dirs.rootSubdir("plugins", "data");
144
213
  }
145
214
 
146
215
  /** Where npm installs packages (~/.omp/plugins/node_modules). */
147
216
  export function getPluginsNodeModules(): string {
148
- return getRootSubdir("plugins/node_modules");
217
+ return path.join(getPluginsDir(), "node_modules");
149
218
  }
150
219
 
151
220
  /** Plugin manifest (~/.omp/plugins/package.json). */
152
221
  export function getPluginsPackageJson(): string {
153
- return getRootSubdir("plugins/package.json");
222
+ return path.join(getPluginsDir(), "package.json");
154
223
  }
155
224
 
156
225
  /** Plugin lock file (~/.omp/plugins/omp-plugins.lock.json). */
157
226
  export function getPluginsLockfile(): string {
158
- return getRootSubdir("plugins/omp-plugins.lock.json");
227
+ return path.join(getPluginsDir(), "omp-plugins.lock.json");
159
228
  }
160
229
 
161
230
  /** Get the remote mount directory (~/.omp/remote). */
162
231
  export function getRemoteDir(): string {
163
- return getRootSubdir("remote");
232
+ return dirs.rootSubdir("remote", "data");
164
233
  }
165
234
 
166
235
  /** Get the SSH control socket directory (~/.omp/ssh-control). */
167
236
  export function getSshControlDir(): string {
168
- return getRootSubdir("ssh-control");
237
+ return dirs.rootSubdir("ssh-control", "state");
169
238
  }
170
239
 
171
240
  /** Get the remote host info directory (~/.omp/remote-host). */
172
241
  export function getRemoteHostDir(): string {
173
- return getRootSubdir("remote-host");
242
+ return dirs.rootSubdir("remote-host", "data");
174
243
  }
175
244
 
176
245
  /** Get the managed Python venv directory (~/.omp/python-env). */
177
246
  export function getPythonEnvDir(): string {
178
- return getRootSubdir("python-env");
247
+ return dirs.rootSubdir("python-env", "data");
179
248
  }
180
249
 
181
250
  /** Get the puppeteer sandbox directory (~/.omp/puppeteer). */
182
251
  export function getPuppeteerDir(): string {
183
- return getRootSubdir("puppeteer");
252
+ return dirs.rootSubdir("puppeteer", "cache");
184
253
  }
185
254
 
186
255
  /** Get the worktree base directory (~/.omp/wt). */
187
256
  export function getWorktreeBaseDir(): string {
188
- return getRootSubdir("wt");
257
+ return dirs.rootSubdir("wt", "data");
189
258
  }
190
259
 
191
260
  /** Get the path to a worktree directory (~/.omp/wt/<project>/<id>). */
@@ -195,17 +264,17 @@ export function getWorktreeDir(encodedProject: string, id: string): string {
195
264
 
196
265
  /** Get the GPU cache path (~/.omp/gpu_cache.json). */
197
266
  export function getGpuCachePath(): string {
198
- return getRootSubdir("gpu_cache.json");
267
+ return dirs.rootSubdir("gpu_cache.json", "cache");
199
268
  }
200
269
 
201
270
  /** Get the natives directory (~/.omp/natives). */
202
271
  export function getNativesDir(): string {
203
- return getRootSubdir("natives");
272
+ return dirs.rootSubdir("natives", "cache");
204
273
  }
205
274
 
206
275
  /** Get the stats database path (~/.omp/stats.db). */
207
276
  export function getStatsDbPath(): string {
208
- return getRootSubdir("stats.db");
277
+ return dirs.rootSubdir("stats.db", "data");
209
278
  }
210
279
 
211
280
  // =============================================================================
@@ -214,57 +283,72 @@ export function getStatsDbPath(): string {
214
283
 
215
284
  /** Get the path to agent.db (SQLite database for settings and auth storage). */
216
285
  export function getAgentDbPath(agentDir?: string): string {
217
- return getAgentSubdir(agentDir, "agent.db");
286
+ return dirs.agentSubdir(agentDir, "agent.db", "data");
287
+ }
288
+
289
+ /** Get the path to history.db (SQLite database for session history). */
290
+ export function getHistoryDbPath(agentDir?: string): string {
291
+ return dirs.agentSubdir(agentDir, "history.db", "data");
292
+ }
293
+
294
+ /** Get the path to models.db (model cache database). */
295
+ export function getModelDbPath(agentDir?: string): string {
296
+ return dirs.agentSubdir(agentDir, "models.db", "data");
218
297
  }
219
298
 
220
299
  /** Get the sessions directory (~/.omp/agent/sessions). */
221
300
  export function getSessionsDir(agentDir?: string): string {
222
- return getAgentSubdir(agentDir, "sessions");
301
+ return dirs.agentSubdir(agentDir, "sessions", "data");
223
302
  }
224
303
 
225
304
  /** Get the content-addressed blob store directory (~/.omp/agent/blobs). */
226
305
  export function getBlobsDir(agentDir?: string): string {
227
- return getAgentSubdir(agentDir, "blobs");
306
+ return dirs.agentSubdir(agentDir, "blobs", "data");
228
307
  }
229
308
 
230
309
  /** Get the custom themes directory (~/.omp/agent/themes). */
231
310
  export function getCustomThemesDir(agentDir?: string): string {
232
- return getAgentSubdir(agentDir, "themes");
311
+ return dirs.agentSubdir(agentDir, "themes");
233
312
  }
234
313
 
235
314
  /** Get the tools directory (~/.omp/agent/tools). */
236
315
  export function getToolsDir(agentDir?: string): string {
237
- return getAgentSubdir(agentDir, "tools");
316
+ return dirs.agentSubdir(agentDir, "tools");
238
317
  }
239
318
 
240
319
  /** Get the slash commands directory (~/.omp/agent/commands). */
241
320
  export function getCommandsDir(agentDir?: string): string {
242
- return getAgentSubdir(agentDir, "commands");
321
+ return dirs.agentSubdir(agentDir, "commands");
243
322
  }
244
323
 
245
324
  /** Get the prompts directory (~/.omp/agent/prompts). */
246
325
  export function getPromptsDir(agentDir?: string): string {
247
- return getAgentSubdir(agentDir, "prompts");
326
+ return dirs.agentSubdir(agentDir, "prompts");
248
327
  }
249
328
 
250
329
  /** Get the user-level Python modules directory (~/.omp/agent/modules). */
251
330
  export function getAgentModulesDir(agentDir?: string): string {
252
- return getAgentSubdir(agentDir, "modules");
331
+ return dirs.agentSubdir(agentDir, "modules");
332
+ }
333
+
334
+ /** Get the memories directory (~/.omp/agent/memories). */
335
+ export function getMemoriesDir(agentDir?: string): string {
336
+ return dirs.agentSubdir(agentDir, "memories", "state");
253
337
  }
254
338
 
255
- /** Get the test auth database path (~/.omp/agent/testauth.db). */
256
- export function getTestAuthPath(agentDir?: string): string {
257
- return getAgentSubdir(agentDir, "testauth.db");
339
+ /** Get the terminal sessions directory (~/.omp/agent/terminal-sessions). */
340
+ export function getTerminalSessionsDir(agentDir?: string): string {
341
+ return dirs.agentSubdir(agentDir, "terminal-sessions", "state");
258
342
  }
259
343
 
260
344
  /** Get the crash log path (~/.omp/agent/omp-crash.log). */
261
345
  export function getCrashLogPath(agentDir?: string): string {
262
- return getAgentSubdir(agentDir, "omp-crash.log");
346
+ return dirs.agentSubdir(agentDir, "omp-crash.log", "state");
263
347
  }
264
348
 
265
349
  /** Get the debug log path (~/.omp/agent/omp-debug.log). */
266
350
  export function getDebugLogPath(agentDir?: string): string {
267
- return getAgentSubdir(agentDir, `${APP_NAME}-debug.log`);
351
+ return dirs.agentSubdir(agentDir, `${APP_NAME}-debug.log`, "state");
268
352
  }
269
353
 
270
354
  // =============================================================================
package/src/ptree.ts CHANGED
@@ -29,7 +29,7 @@ export abstract class Exception extends Error {
29
29
  super(message);
30
30
  this.name = this.constructor.name;
31
31
  }
32
- abstract get aborted(): boolean;
32
+ abstract readonly aborted: boolean;
33
33
  }
34
34
 
35
35
  /** Exception for nonzero exit codes (not cancellation). */