coding-tool-x 3.6.12 → 3.7.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 (80) hide show
  1. package/README.md +46 -33
  2. package/dist/web/assets/{Analytics-BuBlsbcI.js → Analytics-Dma_j2Rq.js} +1 -1
  3. package/dist/web/assets/{ConfigTemplates-DQczxuOX.js → ConfigTemplates-C49Y1PLH.js} +1 -1
  4. package/dist/web/assets/Home-DGGC8H4g.js +1 -0
  5. package/dist/web/assets/Home-ewgeIhF7.css +1 -0
  6. package/dist/web/assets/{PluginManager-C8gAlaps.js → PluginManager-B1ECTHyY.js} +1 -1
  7. package/dist/web/assets/{ProjectList-DL4JK6ci.css → ProjectList-B_L55-l9.css} +1 -1
  8. package/dist/web/assets/ProjectList-DyiGyT-H.js +1 -0
  9. package/dist/web/assets/SessionList-CH5GwwiJ.js +1 -0
  10. package/dist/web/assets/{SkillManager-BDbNbNkK.js → SkillManager-CRyVhjYQ.js} +1 -1
  11. package/dist/web/assets/{WorkspaceManager-DGGaGW2c.js → WorkspaceManager-CO9tclVt.js} +1 -1
  12. package/dist/web/assets/{icons-QFXzEgqM.js → icons-Dop5j7Bf.js} +1 -1
  13. package/dist/web/assets/index-CM26UrnJ.css +1 -0
  14. package/dist/web/assets/index-EQq56pRa.js +2 -0
  15. package/dist/web/assets/{naive-ui-kcWDeJwl.js → naive-ui-CVyvdmg1.js} +1 -1
  16. package/dist/web/index.html +4 -4
  17. package/package.json +2 -1
  18. package/src/commands/channels.js +85 -24
  19. package/src/commands/daemon.js +7 -4
  20. package/src/commands/doctor.js +17 -8
  21. package/src/commands/logs.js +48 -6
  22. package/src/commands/port-config.js +81 -75
  23. package/src/commands/proxy-control.js +52 -19
  24. package/src/commands/stats.js +37 -19
  25. package/src/commands/toggle-proxy.js +131 -53
  26. package/src/commands/ui.js +3 -4
  27. package/src/config/default.js +14 -3
  28. package/src/config/paths.js +99 -11
  29. package/src/index.js +11 -16
  30. package/src/server/api/commands.js +1 -1
  31. package/src/server/api/config-export.js +4 -2
  32. package/src/server/api/config-registry.js +40 -34
  33. package/src/server/api/config.js +2 -1
  34. package/src/server/api/dashboard.js +22 -6
  35. package/src/server/api/mcp.js +5 -3
  36. package/src/server/api/pi-channels.js +349 -0
  37. package/src/server/api/pi-config.js +164 -0
  38. package/src/server/api/pi-projects.js +76 -0
  39. package/src/server/api/pi-proxy.js +154 -0
  40. package/src/server/api/pi-sessions.js +334 -0
  41. package/src/server/api/pi-statistics.js +40 -0
  42. package/src/server/api/plugins.js +1 -1
  43. package/src/server/api/prompts.js +3 -2
  44. package/src/server/api/skills.js +1 -1
  45. package/src/server/api/workspaces.js +2 -2
  46. package/src/server/index.js +41 -0
  47. package/src/server/pi-proxy-server.js +66 -0
  48. package/src/server/services/agents-service.js +2 -1
  49. package/src/server/services/base/proxy-utils.js +1 -0
  50. package/src/server/services/channel-balance.js +151 -1
  51. package/src/server/services/channel-scheduler.js +7 -1
  52. package/src/server/services/commands-service.js +11 -2
  53. package/src/server/services/config-export-service.js +275 -18
  54. package/src/server/services/config-registry-service.js +20 -18
  55. package/src/server/services/config-sync-manager.js +115 -6
  56. package/src/server/services/config-sync-service.js +4 -2
  57. package/src/server/services/config-templates-service.js +183 -45
  58. package/src/server/services/mcp-service.js +217 -33
  59. package/src/server/services/pi-auth-providers.js +294 -0
  60. package/src/server/services/pi-channels.js +94 -0
  61. package/src/server/services/pi-config.js +287 -0
  62. package/src/server/services/pi-sessions.js +490 -0
  63. package/src/server/services/pi-settings-manager.js +571 -0
  64. package/src/server/services/pi-statistics-service.js +317 -0
  65. package/src/server/services/plugins-service.js +673 -11
  66. package/src/server/services/prompts-service.js +112 -7
  67. package/src/server/services/proxy-log-helper.js +1 -0
  68. package/src/server/services/proxy-runtime.js +5 -1
  69. package/src/server/services/skill-service.js +13 -3
  70. package/src/server/services/speed-test.js +9 -3
  71. package/src/server/services/ui-config.js +50 -12
  72. package/src/server/services/workspace-service.js +19 -5
  73. package/src/server/websocket-server.js +7 -1
  74. package/dist/web/assets/Home-BvAzzWnB.js +0 -1
  75. package/dist/web/assets/Home-CIua-b-o.css +0 -1
  76. package/dist/web/assets/ProjectList-nqgi6_cR.js +0 -1
  77. package/dist/web/assets/SessionList-D8AxMYy6.js +0 -1
  78. package/dist/web/assets/index-Dsh4Q5Cg.css +0 -1
  79. package/dist/web/assets/index-y-eEayxR.js +0 -2
  80. package/src/utils/cli-flags.js +0 -7
@@ -0,0 +1,490 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { execFileSync } = require('child_process');
4
+ const { PATHS, HOME_DIR } = require('../../config/paths');
5
+ const { getPiCommand, getPiPaths, isPiInstalled, resolvePiRuntime } = require('./pi-config');
6
+
7
+ const PROJECT_ORDER_FILE = PATHS.piProjectOrder;
8
+ const SESSION_ORDER_FILE = PATHS.piSessionOrder;
9
+
10
+ function ensureDir(dirPath) {
11
+ if (!fs.existsSync(dirPath)) {
12
+ fs.mkdirSync(dirPath, { recursive: true });
13
+ }
14
+ }
15
+
16
+ function safeReadJson(filePath, fallback = {}) {
17
+ try {
18
+ if (!fs.existsSync(filePath)) return fallback;
19
+ const raw = fs.readFileSync(filePath, 'utf8');
20
+ return raw.trim() ? JSON.parse(raw) : fallback;
21
+ } catch {
22
+ return fallback;
23
+ }
24
+ }
25
+
26
+ function safeWriteJson(filePath, value) {
27
+ ensureDir(path.dirname(filePath));
28
+ fs.writeFileSync(filePath, JSON.stringify(value || {}, null, 2), 'utf8');
29
+ }
30
+
31
+ function encodeProjectName(cwd = '') {
32
+ return `--${String(cwd || '').replace(/\\/g, '/').replace(/\//g, '--')}--`;
33
+ }
34
+
35
+ function decodeProjectName(projectName = '') {
36
+ const value = String(projectName || '');
37
+ if (!value.startsWith('--') || !value.endsWith('--')) {
38
+ return value;
39
+ }
40
+ const inner = value.slice(2, -2);
41
+ return `/${inner.split('--').filter(Boolean).join('/')}`;
42
+ }
43
+
44
+ function makeProjectName(cwd = '') {
45
+ const normalized = String(cwd || '').trim();
46
+ return normalized ? encodeProjectName(normalized) : 'unknown';
47
+ }
48
+
49
+ function getDisplayName(cwd = '') {
50
+ const normalized = String(cwd || '').trim();
51
+ if (!normalized) return 'Unknown';
52
+ return path.basename(normalized) || normalized;
53
+ }
54
+
55
+ function parsePiSessionId(filePath = '') {
56
+ const base = path.basename(filePath, '.jsonl');
57
+ const match = base.match(/([0-9a-f]{8}-[0-9a-f-]{8,})$/i);
58
+ if (match) return match[1];
59
+ return base;
60
+ }
61
+
62
+ function normalizeTextContent(content) {
63
+ if (typeof content === 'string') return content;
64
+ if (Array.isArray(content)) {
65
+ return content.map((item) => {
66
+ if (typeof item === 'string') return item;
67
+ if (item?.type === 'text') return item.text || '';
68
+ if (item?.text) return item.text;
69
+ return '';
70
+ }).filter(Boolean).join('\n');
71
+ }
72
+ if (content && typeof content === 'object') {
73
+ if (typeof content.text === 'string') return content.text;
74
+ if (typeof content.content === 'string') return content.content;
75
+ }
76
+ return '';
77
+ }
78
+
79
+ function extractMessageText(message = {}) {
80
+ if (typeof message.content === 'string' || Array.isArray(message.content)) {
81
+ return normalizeTextContent(message.content);
82
+ }
83
+ if (Array.isArray(message.parts)) {
84
+ return normalizeTextContent(message.parts);
85
+ }
86
+ if (Array.isArray(message.thinking)) {
87
+ return normalizeTextContent(message.thinking);
88
+ }
89
+ if (message.toolCall && typeof message.toolCall === 'object') {
90
+ return JSON.stringify(message.toolCall, null, 2);
91
+ }
92
+ if (message.toolResult && typeof message.toolResult === 'object') {
93
+ return normalizeTextContent(message.toolResult.content || message.toolResult.output || message.toolResult);
94
+ }
95
+ if (message.bashExecution && typeof message.bashExecution === 'object') {
96
+ return normalizeTextContent(message.bashExecution.output || message.bashExecution.command || message.bashExecution);
97
+ }
98
+ if (typeof message.text === 'string') {
99
+ return message.text;
100
+ }
101
+ if (typeof message.output === 'string') {
102
+ return message.output;
103
+ }
104
+ if (typeof message.result === 'string') {
105
+ return message.result;
106
+ }
107
+ return '';
108
+ }
109
+
110
+ function parseUsage(usage = {}) {
111
+ const input = Number(usage.input ?? usage.inputTokens ?? usage.input_tokens ?? usage.prompt_tokens ?? usage.promptTokens ?? 0) || 0;
112
+ const output = Number(usage.output ?? usage.outputTokens ?? usage.output_tokens ?? usage.completion_tokens ?? usage.completionTokens ?? 0) || 0;
113
+ const cacheRead = Number(usage.cacheRead ?? usage.cache_read ?? usage.cachedTokens ?? usage.cached_tokens ?? usage.cache_read_input_tokens ?? 0) || 0;
114
+ const cacheWrite = Number(usage.cacheWrite ?? usage.cache_write ?? usage.cacheCreation ?? usage.cache_creation ?? usage.cache_creation_input_tokens ?? 0) || 0;
115
+ const reasoning = Number(usage.reasoningTokens ?? usage.reasoning_tokens ?? 0) || 0;
116
+ const total = Number(usage.totalTokens ?? usage.total_tokens ?? (input + output + cacheRead + cacheWrite + reasoning)) || 0;
117
+ const cost = typeof usage.cost === 'number'
118
+ ? usage.cost
119
+ : Number(usage.cost?.total ?? usage.cost?.usd ?? 0) || 0;
120
+ return { input, output, cached: cacheRead, cacheRead, cacheWrite, reasoning, total, cost };
121
+ }
122
+
123
+ function readJsonLines(filePath) {
124
+ try {
125
+ const raw = fs.readFileSync(filePath, 'utf8');
126
+ return raw
127
+ .split(/\r?\n/)
128
+ .filter(line => line.trim())
129
+ .map((line) => {
130
+ try {
131
+ return JSON.parse(line);
132
+ } catch {
133
+ return { type: 'invalid', raw: line };
134
+ }
135
+ });
136
+ } catch {
137
+ return [];
138
+ }
139
+ }
140
+
141
+ function convertPiEntry(entry = {}, index = 0) {
142
+ if (!entry || entry.type !== 'message') {
143
+ return null;
144
+ }
145
+ const message = entry.message || {};
146
+ const role = message.role || entry.role;
147
+ const assistantLikeRoles = new Set(['assistant', 'tool', 'toolResult', 'tool_result', 'bashExecution', 'bash_execution', 'custom']);
148
+ if (role !== 'user' && !assistantLikeRoles.has(role)) {
149
+ return null;
150
+ }
151
+ const text = extractMessageText(message);
152
+ const type = role === 'user' ? 'user' : 'assistant';
153
+ const result = {
154
+ id: entry.id || message.id || `msg-${index}`,
155
+ type,
156
+ role,
157
+ content: text || message.content || '',
158
+ timestamp: entry.timestamp || message.timestamp || null
159
+ };
160
+
161
+ if (type === 'assistant') {
162
+ if (role !== 'assistant') {
163
+ result.subtype = role;
164
+ }
165
+ result.provider = entry.provider || message.provider || '';
166
+ result.model = entry.model || message.model || '';
167
+ result.usage = parseUsage(entry.usage || message.usage || {});
168
+ }
169
+
170
+ return result;
171
+ }
172
+
173
+ function parseSessionFile(filePath) {
174
+ const stat = fs.statSync(filePath);
175
+ const entries = readJsonLines(filePath);
176
+ const header = entries.find(entry => entry?.type === 'session') || {};
177
+ const messages = entries.map(convertPiEntry).filter(Boolean);
178
+ const modelChange = [...entries].reverse().find(entry => entry?.type === 'model_change');
179
+ const firstUser = messages.find(message => message.type === 'user');
180
+ const lastAssistant = [...messages].reverse().find(message => message.type === 'assistant') || {};
181
+ const cwd = header.cwd || messages.find(message => message.cwd)?.cwd || path.dirname(filePath);
182
+ const sessionId = header.id || parsePiSessionId(filePath);
183
+ const usage = messages.reduce((acc, message) => {
184
+ if (message.type !== 'assistant') return acc;
185
+ const current = parseUsage(message.usage || {});
186
+ acc.input += current.input;
187
+ acc.output += current.output;
188
+ acc.cached += current.cached;
189
+ acc.reasoning += current.reasoning;
190
+ acc.total += current.total;
191
+ return acc;
192
+ }, { input: 0, output: 0, cached: 0, reasoning: 0, total: 0 });
193
+
194
+ return {
195
+ sessionId,
196
+ filePath,
197
+ cwd,
198
+ directory: cwd,
199
+ projectName: makeProjectName(cwd),
200
+ preview: firstUser?.content || '',
201
+ firstMessage: firstUser?.content || null,
202
+ messageCount: messages.length,
203
+ model: modelChange?.modelId || modelChange?.model || lastAssistant.model || '',
204
+ provider: modelChange?.provider || lastAssistant.provider || '',
205
+ usage,
206
+ size: stat.size,
207
+ mtime: new Date(stat.mtimeMs).toISOString(),
208
+ mtimeMs: stat.mtimeMs,
209
+ timestamp: header.timestamp || null,
210
+ source: 'pi'
211
+ };
212
+ }
213
+
214
+ function scanSessionFiles(rootDir = getPiPaths().sessions) {
215
+ if (!fs.existsSync(rootDir)) {
216
+ return [];
217
+ }
218
+ const files = [];
219
+ const walk = (dir) => {
220
+ let entries = [];
221
+ try {
222
+ entries = fs.readdirSync(dir, { withFileTypes: true });
223
+ } catch {
224
+ return;
225
+ }
226
+ entries.forEach((entry) => {
227
+ const fullPath = path.join(dir, entry.name);
228
+ if (entry.isDirectory()) {
229
+ walk(fullPath);
230
+ } else if (entry.isFile() && entry.name.endsWith('.jsonl')) {
231
+ files.push(fullPath);
232
+ }
233
+ });
234
+ };
235
+ walk(rootDir);
236
+ return files;
237
+ }
238
+
239
+ function getAllSessions() {
240
+ return scanSessionFiles()
241
+ .map((filePath) => {
242
+ try {
243
+ return parseSessionFile(filePath);
244
+ } catch (error) {
245
+ console.warn('[OMP Sessions] Failed to parse session:', filePath, error.message);
246
+ return null;
247
+ }
248
+ })
249
+ .filter(Boolean)
250
+ .sort((a, b) => (b.mtimeMs || 0) - (a.mtimeMs || 0));
251
+ }
252
+
253
+ function loadProjectOrder() {
254
+ const data = safeReadJson(PROJECT_ORDER_FILE, { order: [] });
255
+ return Array.isArray(data.order) ? data.order : [];
256
+ }
257
+
258
+ function loadSessionOrder() {
259
+ return safeReadJson(SESSION_ORDER_FILE, {});
260
+ }
261
+
262
+ function getProjects() {
263
+ const sessions = getAllSessions();
264
+ const projectMap = new Map();
265
+ sessions.forEach((session) => {
266
+ const key = session.projectName;
267
+ const existing = projectMap.get(key) || {
268
+ name: key,
269
+ path: session.cwd,
270
+ fullPath: session.cwd,
271
+ displayName: getDisplayName(session.cwd),
272
+ sessionCount: 0,
273
+ latestSession: null,
274
+ mtime: session.mtime,
275
+ mtimeMs: 0,
276
+ source: 'pi'
277
+ };
278
+ existing.sessionCount += 1;
279
+ if ((session.mtimeMs || 0) > (existing.mtimeMs || 0)) {
280
+ existing.latestSession = session.sessionId;
281
+ existing.mtime = session.mtime;
282
+ existing.mtimeMs = session.mtimeMs;
283
+ }
284
+ projectMap.set(key, existing);
285
+ });
286
+
287
+ const projects = Array.from(projectMap.values());
288
+ const order = loadProjectOrder();
289
+ projects.sort((a, b) => {
290
+ const ai = order.indexOf(a.name);
291
+ const bi = order.indexOf(b.name);
292
+ if (ai !== -1 || bi !== -1) {
293
+ if (ai === -1) return 1;
294
+ if (bi === -1) return -1;
295
+ return ai - bi;
296
+ }
297
+ return (b.mtimeMs || 0) - (a.mtimeMs || 0);
298
+ });
299
+ return projects;
300
+ }
301
+
302
+ function getSessionsByProject(projectName) {
303
+ const orderMap = loadSessionOrder();
304
+ const order = Array.isArray(orderMap[projectName]) ? orderMap[projectName] : [];
305
+ const sessions = getAllSessions().filter(session => session.projectName === projectName);
306
+ sessions.sort((a, b) => {
307
+ const ai = order.indexOf(a.sessionId);
308
+ const bi = order.indexOf(b.sessionId);
309
+ if (ai !== -1 || bi !== -1) {
310
+ if (ai === -1) return 1;
311
+ if (bi === -1) return -1;
312
+ return ai - bi;
313
+ }
314
+ return (b.mtimeMs || 0) - (a.mtimeMs || 0);
315
+ });
316
+ return sessions.map(normalizeSession);
317
+ }
318
+
319
+ function normalizeSession(session) {
320
+ if (!session) return null;
321
+ return {
322
+ sessionId: session.sessionId,
323
+ filePath: session.filePath,
324
+ firstMessage: session.firstMessage || null,
325
+ gitBranch: null,
326
+ forkedFrom: null,
327
+ source: 'pi',
328
+ provider: session.provider || '',
329
+ model: session.model || '',
330
+ size: session.size || 0,
331
+ mtime: session.mtime,
332
+ mtimeMs: session.mtimeMs,
333
+ directory: session.directory || session.cwd || null,
334
+ messageCount: session.messageCount || 0,
335
+ tokens: session.usage || { input: 0, output: 0, cached: 0, reasoning: 0, total: 0 }
336
+ };
337
+ }
338
+
339
+ function getSessionById(sessionId) {
340
+ return getAllSessions().find(session => session.sessionId === sessionId || path.basename(session.filePath, '.jsonl') === sessionId) || null;
341
+ }
342
+
343
+ function getSessionMessages(sessionId) {
344
+ const session = getSessionById(sessionId);
345
+ if (!session) {
346
+ throw new Error('Session not found');
347
+ }
348
+ return readJsonLines(session.filePath).map(convertPiEntry).filter(Boolean);
349
+ }
350
+
351
+ function getRecentSessions(limit = 5) {
352
+ return getAllSessions().slice(0, limit).map(normalizeSession);
353
+ }
354
+
355
+ function searchSessions(keyword, contextLength = 35, projectName = null) {
356
+ const needle = String(keyword || '').toLowerCase();
357
+ if (!needle) return [];
358
+
359
+ return getAllSessions()
360
+ .filter(session => !projectName || session.projectName === projectName)
361
+ .map((session) => {
362
+ const messages = readJsonLines(session.filePath).map(convertPiEntry).filter(Boolean);
363
+ const matches = [];
364
+ messages.forEach((message) => {
365
+ const text = String(message.content || '');
366
+ const index = text.toLowerCase().indexOf(needle);
367
+ if (index === -1) return;
368
+ const start = Math.max(0, index - contextLength);
369
+ const end = Math.min(text.length, index + needle.length + contextLength);
370
+ matches.push({
371
+ role: message.role || message.type,
372
+ context: text.slice(start, end),
373
+ timestamp: message.timestamp || null
374
+ });
375
+ });
376
+ if (matches.length === 0) return null;
377
+ return {
378
+ ...normalizeSession(session),
379
+ projectName: session.projectName,
380
+ projectDisplayName: getDisplayName(session.cwd),
381
+ matchCount: matches.length,
382
+ matches
383
+ };
384
+ })
385
+ .filter(Boolean);
386
+ }
387
+
388
+ function deleteSession(sessionId) {
389
+ const session = getSessionById(sessionId);
390
+ if (!session) {
391
+ throw new Error('Session not found');
392
+ }
393
+ fs.unlinkSync(session.filePath);
394
+ return { success: true };
395
+ }
396
+
397
+ function deleteProject(projectName) {
398
+ const sessions = getAllSessions().filter(session => session.projectName === projectName);
399
+ if (sessions.length === 0) {
400
+ throw new Error('Project not found');
401
+ }
402
+ sessions.forEach(session => fs.unlinkSync(session.filePath));
403
+ return { success: true, deletedSessions: sessions.length };
404
+ }
405
+
406
+ function forkSession(sessionId) {
407
+ const session = getSessionById(sessionId);
408
+ if (!session) {
409
+ throw new Error('Session not found');
410
+ }
411
+ const parsed = path.parse(session.filePath);
412
+ const forkId = `fork-${Date.now()}`;
413
+ const targetPath = path.join(parsed.dir, `${parsed.name}-${forkId}${parsed.ext}`);
414
+ fs.copyFileSync(session.filePath, targetPath);
415
+ return { success: true, newSessionId: parsePiSessionId(targetPath), filePath: targetPath };
416
+ }
417
+
418
+ function saveProjectOrder(order) {
419
+ safeWriteJson(PROJECT_ORDER_FILE, { order: Array.isArray(order) ? order : [] });
420
+ }
421
+
422
+ function saveSessionOrder(projectName, order) {
423
+ const data = loadSessionOrder();
424
+ data[projectName] = Array.isArray(order) ? order : [];
425
+ safeWriteJson(SESSION_ORDER_FILE, data);
426
+ }
427
+
428
+ function getProjectAndSessionCounts() {
429
+ const sessions = getAllSessions();
430
+ return {
431
+ projectCount: new Set(sessions.map(session => session.projectName)).size,
432
+ sessionCount: sessions.length
433
+ };
434
+ }
435
+
436
+ function buildLaunchCommand(sessionId, cwd, options = {}) {
437
+ const args = [];
438
+ if (options.rpc) {
439
+ args.push('--mode rpc');
440
+ }
441
+ if (options.sessionDir) {
442
+ args.push('--session-dir', shellQuote(options.sessionDir));
443
+ }
444
+ if (options.fork) {
445
+ args.push('--fork', shellQuote(sessionId));
446
+ } else {
447
+ args.push('--session', shellQuote(sessionId));
448
+ }
449
+ return `${getPiCommand()} ${args.join(' ')}`;
450
+ }
451
+
452
+ function shellQuote(value) {
453
+ return `"${String(value).replace(/"/g, '\\"')}"`;
454
+ }
455
+
456
+ function isPiCliInstalled() {
457
+ if (isPiInstalled()) return true;
458
+ const runtime = resolvePiRuntime();
459
+ if (runtime.installed) return true;
460
+ try {
461
+ execFileSync(getPiCommand(), ['--version'], { stdio: 'ignore', timeout: 3000 });
462
+ return true;
463
+ } catch {
464
+ return false;
465
+ }
466
+ }
467
+
468
+ module.exports = {
469
+ buildLaunchCommand,
470
+ decodeProjectName,
471
+ deleteProject,
472
+ deleteSession,
473
+ encodeProjectName,
474
+ forkSession,
475
+ getAllSessions,
476
+ getProjectAndSessionCounts,
477
+ getProjects,
478
+ getRecentSessions,
479
+ getSessionById,
480
+ getSessionMessages,
481
+ getSessionsByProject,
482
+ isPiInstalled: isPiCliInstalled,
483
+ normalizeSession,
484
+ parseSessionFile,
485
+ scanSessionFiles,
486
+ searchSessions,
487
+ saveProjectOrder,
488
+ saveSessionOrder,
489
+ HOME_DIR
490
+ };