codexmate 0.1.23 → 0.1.25

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 (37) hide show
  1. package/cli.js +4 -1
  2. package/package.json +1 -1
  3. package/plugins/prompt-templates/computed.mjs +1 -1
  4. package/plugins/prompt-templates/index.mjs +3 -3
  5. package/plugins/prompt-templates/manifest.mjs +5 -3
  6. package/plugins/prompt-templates/methods.mjs +9 -2
  7. package/plugins/registry.mjs +5 -2
  8. package/plugins/text-tools/computed.mjs +23 -0
  9. package/plugins/text-tools/index.mjs +14 -0
  10. package/plugins/text-tools/manifest.mjs +20 -0
  11. package/plugins/text-tools/methods.mjs +24 -0
  12. package/plugins/text-tools/overview.mjs +12 -0
  13. package/plugins/text-tools/ownership.mjs +7 -0
  14. package/web-ui/app.js +5 -2
  15. package/web-ui/index.html +0 -1
  16. package/web-ui/logic.markdown-editor.mjs +5 -57
  17. package/web-ui/logic.prompts-change-axis.mjs +3 -88
  18. package/web-ui/logic.prompts-path-reference.mjs +118 -0
  19. package/web-ui/modules/app.methods.agents.mjs +1 -0
  20. package/web-ui/modules/app.methods.index.mjs +1 -1
  21. package/web-ui/modules/app.methods.navigation.mjs +79 -3
  22. package/web-ui/modules/app.methods.prompts-editor.mjs +140 -145
  23. package/web-ui/modules/app.methods.web-ui-preferences.mjs +7 -1
  24. package/web-ui/modules/i18n/locales/en.mjs +22 -1
  25. package/web-ui/modules/i18n/locales/ja.mjs +22 -1
  26. package/web-ui/modules/i18n/locales/vi.mjs +22 -1
  27. package/web-ui/modules/i18n/locales/zh-tw.mjs +22 -1
  28. package/web-ui/modules/i18n/locales/zh.mjs +22 -1
  29. package/web-ui/modules/plugins.computed.mjs +8 -2
  30. package/web-ui/modules/plugins.methods.mjs +8 -2
  31. package/web-ui/partials/index/layout-header.html +19 -4
  32. package/web-ui/partials/index/panel-plugins.html +45 -5
  33. package/web-ui/partials/index/panel-prompts.html +24 -31
  34. package/web-ui/res/web-ui-render.precompiled.js +142 -130
  35. package/web-ui/session-helpers.mjs +0 -1
  36. package/web-ui/source-bundle.cjs +1 -0
  37. package/web-ui/styles/prompts-editor.css +20 -91
@@ -110,12 +110,17 @@
110
110
  const settingsTab = typeof vm.settingsTab === 'string' ? vm.settingsTab.trim().toLowerCase() : 'general';
111
111
  const skillsTargetApp = typeof vm.skillsTargetApp === 'string' && (vm.skillsTargetApp === 'codex' || vm.skillsTargetApp === 'claude' || vm.skillsTargetApp === 'pi') ? vm.skillsTargetApp : 'codex';
112
112
  const promptTemplatesMode = typeof vm.promptTemplatesMode === 'string' && (vm.promptTemplatesMode === 'compose' || vm.promptTemplatesMode === 'manage') ? vm.promptTemplatesMode : 'compose';
113
+ const pluginsActiveIdSource = resolvedOverrides && typeof resolvedOverrides.pluginsActiveId === 'string'
114
+ ? resolvedOverrides.pluginsActiveId
115
+ : vm.pluginsActiveId;
116
+ const pluginsActiveId = typeof pluginsActiveIdSource === 'string' ? pluginsActiveIdSource.trim() : '';
113
117
  const snapshot = {
114
118
  settingsTab: settingsTab === 'data' ? 'data' : 'general',
115
119
  mainTab: resolveSelectableMainTab(mainTab),
116
120
  configMode: configModeSet && configModeSet.has(configMode) ? configMode : 'codex',
117
121
  skillsTargetApp,
118
- promptTemplatesMode
122
+ promptTemplatesMode,
123
+ pluginsActiveId: pluginsActiveId || 'prompt-templates'
119
124
  };
120
125
  if (typeof vm.persistWebUiPreferences === 'function') {
121
126
  vm.persistWebUiPreferences({ navigation: snapshot });
@@ -192,6 +197,7 @@
192
197
  intent: '',
193
198
  pendingTarget: '',
194
199
  pendingConfigMode: '',
200
+ pendingPluginsId: '',
195
201
  ticket: 0
196
202
  };
197
203
  return this.__mainTabSwitchState;
@@ -242,7 +248,7 @@
242
248
  if (!normalizedKind || !normalizedValue) {
243
249
  return;
244
250
  }
245
- const expectedIntent = normalizedKind === 'config' ? 'config' : normalizedValue;
251
+ const expectedIntent = normalizedKind === 'config' || normalizedKind === 'plugins' ? normalizedKind : normalizedValue;
246
252
  this.cancelTouchNavIntentReset();
247
253
  const token = (Number(this.__touchNavIntentResetToken) || 0) + 1;
248
254
  this.__touchNavIntentResetToken = token;
@@ -259,21 +265,26 @@
259
265
  this.clearMainTabSwitchIntent(expectedIntent);
260
266
  }, 1000);
261
267
  },
262
- applyImmediateNavIntent(tab, configMode = '') {
268
+ applyImmediateNavIntent(tab, configMode = '', pluginsId = '') {
263
269
  if (typeof document === 'undefined') return;
264
270
  const normalizedTab = typeof tab === 'string' ? tab.trim().toLowerCase() : '';
265
271
  if (!normalizedTab) return;
266
272
  const normalizedMode = typeof configMode === 'string' ? configMode.trim().toLowerCase() : '';
273
+ const normalizedPluginId = typeof pluginsId === 'string' ? pluginsId.trim().toLowerCase() : '';
267
274
  const domState = this.ensureImmediateNavDomState();
268
275
  const nodes = Array.isArray(domState.navNodes) ? domState.navNodes : [];
269
276
  for (const node of nodes) {
270
277
  if (!node || !node.classList) continue;
271
278
  const nodeTab = String(node.getAttribute('data-main-tab') || '').trim().toLowerCase();
272
279
  const nodeMode = String(node.getAttribute('data-config-mode') || '').trim().toLowerCase();
280
+ const nodePluginId = String(node.getAttribute('data-plugins-id') || '').trim().toLowerCase();
273
281
  let shouldActivate = nodeTab === normalizedTab;
274
282
  if (shouldActivate && normalizedTab === 'config') {
275
283
  shouldActivate = nodeMode ? nodeMode === normalizedMode : false;
276
284
  }
285
+ if (shouldActivate && normalizedTab === 'plugins') {
286
+ shouldActivate = nodePluginId ? nodePluginId === normalizedPluginId : !normalizedPluginId;
287
+ }
277
288
  node.classList.toggle('nav-intent-active', !!shouldActivate);
278
289
  node.classList.toggle('nav-intent-inactive', !shouldActivate);
279
290
  }
@@ -395,6 +406,67 @@
395
406
  if (this.consumePointerNavCommit('config', normalizedMode)) return;
396
407
  this.switchConfigMode(normalizedMode);
397
408
  },
409
+
410
+ switchPluginsTab(pluginId) {
411
+ const normalizedId = typeof pluginId === 'string' ? pluginId.trim().toLowerCase() : '';
412
+ if (!normalizedId) return;
413
+ if (typeof this.selectPlugin === 'function') {
414
+ this.selectPlugin(normalizedId);
415
+ } else {
416
+ this.pluginsActiveId = normalizedId;
417
+ }
418
+ this.switchMainTab('plugins');
419
+ },
420
+
421
+ onPluginsTabPointerDown(pluginId) {
422
+ const event = arguments.length > 1 ? arguments[1] : null;
423
+ if (event && typeof event.button === 'number' && event.button !== 0) {
424
+ return;
425
+ }
426
+ const normalizedId = typeof pluginId === 'string' ? pluginId.trim().toLowerCase() : '';
427
+ if (!normalizedId) return;
428
+ persistNavState(this, { mainTab: 'plugins', pluginsActiveId: normalizedId });
429
+ this.setMainTabSwitchIntent('plugins');
430
+ if (typeof this.ensureMainTabSwitchState === 'function') {
431
+ this.ensureMainTabSwitchState().pendingPluginsId = normalizedId;
432
+ }
433
+ this.applyImmediateNavIntent('plugins', '', normalizedId);
434
+ const shouldHideSessionPanel = this.mainTab === 'sessions';
435
+ this.setSessionPanelFastHidden(shouldHideSessionPanel);
436
+ const pointerType = event && typeof event.pointerType === 'string'
437
+ ? event.pointerType.trim().toLowerCase()
438
+ : '';
439
+ if (pointerType === 'touch') {
440
+ this.scheduleTouchNavIntentReset('plugins', normalizedId);
441
+ return;
442
+ }
443
+ this.recordPointerNavCommit('plugins', normalizedId);
444
+ this.switchPluginsTab(normalizedId);
445
+ },
446
+
447
+ onPluginsTabClick(pluginId) {
448
+ const normalizedId = typeof pluginId === 'string' ? pluginId.trim().toLowerCase() : '';
449
+ if (!normalizedId) return;
450
+ if (this.consumePointerNavCommit('plugins', normalizedId)) return;
451
+ this.switchPluginsTab(normalizedId);
452
+ },
453
+
454
+ isPluginsIdNavActive(pluginId) {
455
+ const normalizedId = typeof pluginId === 'string' ? pluginId.trim().toLowerCase() : '';
456
+ if (!normalizedId) return false;
457
+ if (!this.isMainTabNavActive('plugins')) return false;
458
+ const state = this.ensureMainTabSwitchState();
459
+ const pendingId = typeof state.pendingPluginsId === 'string'
460
+ ? state.pendingPluginsId.trim().toLowerCase()
461
+ : '';
462
+ if (state.intent === 'plugins' && pendingId) {
463
+ return pendingId === normalizedId;
464
+ }
465
+ const currentId = typeof this.pluginsActiveId === 'string'
466
+ ? this.pluginsActiveId.trim().toLowerCase()
467
+ : '';
468
+ return currentId === normalizedId;
469
+ },
398
470
  clearMainTabSwitchIntent(expectedTab = '') {
399
471
  const state = this.ensureMainTabSwitchState();
400
472
  if (expectedTab && state.intent && state.intent !== expectedTab) {
@@ -404,6 +476,7 @@
404
476
  state.intent = '';
405
477
  state.pendingTarget = '';
406
478
  state.pendingConfigMode = '';
479
+ state.pendingPluginsId = '';
407
480
  this.clearImmediateNavIntent();
408
481
  this.setSessionPanelFastHidden(false);
409
482
  },
@@ -463,6 +536,9 @@
463
536
  if (targetTab !== 'config') {
464
537
  switchState.pendingConfigMode = '';
465
538
  }
539
+ if (targetTab !== 'plugins') {
540
+ switchState.pendingPluginsId = '';
541
+ }
466
542
  if (targetTab === previousTab) {
467
543
  switchState.ticket += 1;
468
544
  switchState.pendingTarget = '';
@@ -1,30 +1,29 @@
1
1
  /**
2
- * Prompts-panel Markdown editor methods: overlay highlight sync,
3
- * mobile view switching, and the live split preview (marked + DOMPurify,
4
- * debounced). One implementation is shared by the agentsContent editor
5
- * and the sysPromptContent editor.
2
+ * Prompts-panel Markdown editor methods: mobile view switching and the
3
+ * live split preview (marked + DOMPurify, debounced). One implementation
4
+ * is shared by the agentsContent editor and the sysPromptContent editor.
6
5
  *
7
6
  * State contract (declared in app.js data()):
8
7
  * promptsPreviewEnabled, promptsPreviewCollapsed, promptsMobileView,
9
- * promptsPreviewLibsMissing, agentsHighlightHtml, sysPromptHighlightHtml,
10
- * agentsPreviewHtml, sysPromptPreviewHtml, agentsChangeAxis, sysChangeAxis
8
+ * promptsPreviewLibsMissing, agentsPreviewHtml, sysPromptPreviewHtml,
9
+ * agentsChangeAxis, sysChangeAxis,
10
+ * promptsPathReferencePaths, promptsPathReferenceLoading,
11
+ * promptsPathReferenceCacheKey (path-reference dropdown)
11
12
  * Template contract:
12
13
  * refs promptsAgentsTextarea / promptsSysTextarea on the two textareas,
13
- * refs promptsAgentsHighlight / promptsSysHighlight on the backdrop <pre>,
14
14
  * refs promptsAgentsDiffView / promptsSysDiffView on the two diff row containers.
15
15
  */
16
16
 
17
+ import { buildMarkdownPreviewHtml } from '../logic.markdown-editor.mjs';
17
18
  import {
18
- buildMarkdownPreviewHtml,
19
- highlightMarkdownText
20
- } from '../logic.markdown-editor.mjs';
19
+ buildPromptPathReferenceOptions as buildPromptPathReferenceOptionList,
20
+ buildPromptPathReferenceSentence,
21
+ insertPromptPathReferenceText
22
+ } from '../logic.prompts-path-reference.mjs';
21
23
  import {
22
- buildPromptsChangeAxis,
23
- buildPromptsDiffAxis,
24
- computePromptsAxisScrollTop
24
+ buildPromptsDiffAxis
25
25
  } from '../logic.prompts-change-axis.mjs';
26
26
 
27
- const PROMPTS_HIGHLIGHT_DEBOUNCE_MS = 150;
28
27
  const PROMPTS_PREVIEW_DEBOUNCE_MS = 300;
29
28
  const PROMPTS_AXIS_DEBOUNCE_MS = 300;
30
29
 
@@ -32,7 +31,6 @@ const PROMPTS_EDITOR_META = {
32
31
  agents: {
33
32
  contentField: 'agentsContent',
34
33
  originalField: 'agentsOriginalContent',
35
- highlightField: 'agentsHighlightHtml',
36
34
  previewField: 'agentsPreviewHtml',
37
35
  axisField: 'agentsChangeAxis',
38
36
  diffLinesField: 'agentsDiffLines',
@@ -43,13 +41,11 @@ const PROMPTS_EDITOR_META = {
43
41
  diffLoadingField: 'agentsDiffLoading',
44
42
  prepareDiffMethod: 'prepareAgentsDiff',
45
43
  textareaRef: 'promptsAgentsTextarea',
46
- highlightRef: 'promptsAgentsHighlight',
47
44
  diffViewRef: 'promptsAgentsDiffView'
48
45
  },
49
46
  sys: {
50
47
  contentField: 'sysPromptContent',
51
48
  originalField: 'sysPromptOriginalContent',
52
- highlightField: 'sysPromptHighlightHtml',
53
49
  previewField: 'sysPromptPreviewHtml',
54
50
  axisField: 'sysChangeAxis',
55
51
  diffLinesField: 'sysPromptDiffLines',
@@ -60,7 +56,6 @@ const PROMPTS_EDITOR_META = {
60
56
  diffLoadingField: 'sysPromptDiffLoading',
61
57
  prepareDiffMethod: 'prepareSysPromptDiff',
62
58
  textareaRef: 'promptsSysTextarea',
63
- highlightRef: 'promptsSysHighlight',
64
59
  diffViewRef: 'promptsSysDiffView'
65
60
  }
66
61
  };
@@ -69,13 +64,6 @@ function emptyPromptsChangeAxis() {
69
64
  return { mode: 'edit', ticks: [], truncated: false, totalLines: 0, added: 0, removed: 0 };
70
65
  }
71
66
 
72
- function resolvePromptsHljs() {
73
- if (typeof window !== 'undefined' && window.hljs) {
74
- return window.hljs;
75
- }
76
- return null;
77
- }
78
-
79
67
  function resolvePromptsPreviewLibs() {
80
68
  if (typeof window === 'undefined') {
81
69
  return { marked: null, purify: null };
@@ -83,53 +71,15 @@ function resolvePromptsPreviewLibs() {
83
71
  return { marked: window.marked || null, purify: window.DOMPurify || null };
84
72
  }
85
73
 
86
- export function createPromptsEditorMethods() {
74
+ export function createPromptsEditorMethods(options = {}) {
75
+ const { api } = options;
76
+
87
77
  return {
88
78
  promptsEditorMeta(key) {
89
79
  return PROMPTS_EDITOR_META[key] || null;
90
80
  },
91
81
 
92
82
 
93
- refreshPromptsHighlight(key) {
94
- const meta = this.promptsEditorMeta(key);
95
- if (!meta) {
96
- return;
97
- }
98
- this[meta.highlightField] = highlightMarkdownText(this[meta.contentField], resolvePromptsHljs());
99
- this.syncPromptsOverlayScroll(key);
100
- },
101
-
102
- schedulePromptsHighlight(key) {
103
- const meta = this.promptsEditorMeta(key);
104
- if (!meta) {
105
- return;
106
- }
107
- if (!this._promptsHighlightTimers) {
108
- this._promptsHighlightTimers = {};
109
- }
110
- if (this._promptsHighlightTimers[key]) {
111
- clearTimeout(this._promptsHighlightTimers[key]);
112
- }
113
- this._promptsHighlightTimers[key] = setTimeout(() => {
114
- this._promptsHighlightTimers[key] = null;
115
- this.refreshPromptsHighlight(key);
116
- }, PROMPTS_HIGHLIGHT_DEBOUNCE_MS);
117
- },
118
-
119
- syncPromptsOverlayScroll(key) {
120
- const meta = this.promptsEditorMeta(key);
121
- if (!meta || typeof this.$refs !== 'object' || !this.$refs) {
122
- return;
123
- }
124
- const textarea = this.$refs[meta.textareaRef];
125
- const backdrop = this.$refs[meta.highlightRef];
126
- if (!textarea || !backdrop) {
127
- return;
128
- }
129
- backdrop.scrollTop = textarea.scrollTop;
130
- backdrop.scrollLeft = textarea.scrollLeft;
131
- },
132
-
133
83
  refreshPromptsPreview(key) {
134
84
  const meta = this.promptsEditorMeta(key);
135
85
  if (!meta) {
@@ -167,7 +117,6 @@ export function createPromptsEditorMethods() {
167
117
  },
168
118
 
169
119
  schedulePromptsEditorRefresh(key) {
170
- this.schedulePromptsHighlight(key);
171
120
  this.schedulePromptsPreview(key);
172
121
  this.schedulePromptsChangeAxis(key);
173
122
  },
@@ -188,25 +137,12 @@ export function createPromptsEditorMethods() {
188
137
  added: Number(stats && stats.added) || 0,
189
138
  removed: Number(stats && stats.removed) || 0
190
139
  };
191
- this.consumePromptsAxisPendingJump(key);
192
- return;
193
- }
194
- this.clearPromptsAxisPendingJump(key);
195
- const current = this[meta.contentField];
196
- const original = this[meta.originalField];
197
- if (typeof current !== 'string' || current === original) {
198
- this[meta.axisField] = emptyPromptsChangeAxis();
199
140
  return;
200
141
  }
201
- const axis = buildPromptsChangeAxis(typeof original === 'string' ? original : '', current);
202
- this[meta.axisField] = {
203
- mode: 'edit',
204
- ticks: axis.truncated ? [] : axis.ticks,
205
- truncated: axis.truncated,
206
- totalLines: axis.totalLines,
207
- added: Number(axis.stats && axis.stats.added) || 0,
208
- removed: Number(axis.stats && axis.stats.removed) || 0
209
- };
142
+ // Edit mode renders no axis (the axis is a diff-preview-only
143
+ // affordance): keep it empty so the full line-diff cost is only
144
+ // paid while the red/green preview is open.
145
+ this[meta.axisField] = emptyPromptsChangeAxis();
210
146
  },
211
147
 
212
148
  schedulePromptsChangeAxis(key) {
@@ -241,85 +177,144 @@ export function createPromptsEditorMethods() {
241
177
  }
242
178
  return;
243
179
  }
244
- // Edit mode: a tick click opens the red/green comparison view
245
- // focused on this hunk, so the user sees WHAT changed (old vs new),
246
- // not just where. prepareAgentsDiff / prepareSysPromptDiff are the
247
- // same preview entry the save/eye button uses; both settle their
248
- // own error state internally and never reject.
249
- const canOpenDiff = typeof this[meta.prepareDiffMethod] === 'function'
250
- && !this[meta.loadingField]
251
- && !this[meta.savingField]
252
- && !this[meta.diffLoadingField];
253
- if (canOpenDiff) {
254
- this._promptsAxisPendingJump = { key, startLine: tick.startLine };
255
- this[meta.prepareDiffMethod]();
256
- return;
180
+ },
181
+
182
+ setPromptsMobileView(view) {
183
+ if (view === 'edit' || view === 'preview') {
184
+ this.promptsMobileView = view;
257
185
  }
258
- // Fallback when the diff flow is not wired on the context (isolated
259
- // method usage): keep the legacy proportional editor scroll so the
260
- // click still produces an observable move instead of a silent no-op.
261
- const textarea = this.$refs && this.$refs[meta.textareaRef];
262
- if (!textarea || typeof textarea.scrollTop !== 'number') {
263
- return;
186
+ },
187
+
188
+ togglePromptsPreviewCollapsed() {
189
+ this.promptsPreviewCollapsed = !this.promptsPreviewCollapsed;
190
+ if (typeof this.persistWebUiPreferences === 'function') {
191
+ this.persistWebUiPreferences({ promptsPreviewCollapsed: this.promptsPreviewCollapsed });
264
192
  }
265
- textarea.scrollTop = computePromptsAxisScrollTop(
266
- textarea.scrollHeight,
267
- textarea.clientHeight,
268
- tick.startLine,
269
- axis.totalLines
270
- );
271
- this.syncPromptsOverlayScroll(key);
272
193
  },
273
194
 
274
- clearPromptsAxisPendingJump(key) {
275
- if (this._promptsAxisPendingJump && this._promptsAxisPendingJump.key === key) {
276
- this._promptsAxisPendingJump = null;
195
+ buildPromptsPathReferenceCacheKey() {
196
+ return [
197
+ (this.projectClaudeMdPath || '').trim(),
198
+ this.sysPromptScope || 'global',
199
+ this.sysPromptMode || 'system'
200
+ ].join('|');
201
+ },
202
+
203
+ // Lazy loader for the path-reference dropdown. Fetches the OTHER tabs'
204
+ // real absolute paths via the existing metaOnly RPC contracts
205
+ // (get-agents-file / get-opencode-agents-file / get-claude-md-file
206
+ // return path + exists without content; get-system-prompt returns
207
+ // path + a small SYSTEM.md/APPEND_SYSTEM.md read). Cache key covers the
208
+ // only inputs that change a resolved path: project baseDir, sysPrompt
209
+ // scope and mode. Idempotent per key; concurrent callers share the
210
+ // in-flight promise.
211
+ loadPromptsPathReferences() {
212
+ const cacheKey = this.buildPromptsPathReferenceCacheKey();
213
+ if (this.promptsPathReferenceCacheKey === cacheKey && !this.promptsPathReferenceLoading) {
214
+ return Promise.resolve();
215
+ }
216
+ if (this.promptsPathReferenceLoading && this._promptsPathReferencePromise) {
217
+ return this._promptsPathReferencePromise;
277
218
  }
219
+ if (typeof api !== 'function') {
220
+ return Promise.resolve();
221
+ }
222
+ const projectPath = (this.projectClaudeMdPath || '').trim();
223
+ const scope = this.sysPromptScope || 'global';
224
+ const mode = this.sysPromptMode || 'system';
225
+ const fetchEntry = async (action, params) => {
226
+ try {
227
+ const res = await api(action, params);
228
+ if (res && res.error) {
229
+ return { path: '', exists: false, error: String(res.error) };
230
+ }
231
+ return { path: (res && res.path) || '', exists: !!(res && res.exists) };
232
+ } catch (e) {
233
+ return {
234
+ path: '',
235
+ exists: false,
236
+ error: typeof this.t === 'function' ? this.t('prompts.pathReference.loadFailed') : 'load failed'
237
+ };
238
+ }
239
+ };
240
+ this.promptsPathReferenceLoading = true;
241
+ const promise = Promise.all([
242
+ fetchEntry('get-agents-file', { metaOnly: true }),
243
+ fetchEntry('get-opencode-agents-file', { metaOnly: true }),
244
+ fetchEntry('get-claude-md-file', { metaOnly: true }),
245
+ projectPath
246
+ ? fetchEntry('get-claude-md-file', { baseDir: projectPath, metaOnly: true })
247
+ : Promise.resolve(null),
248
+ fetchEntry('get-system-prompt', { scope, mode })
249
+ ]).then((results) => {
250
+ this.promptsPathReferencePaths = {
251
+ codex: results[0],
252
+ opencode: results[1],
253
+ claudeGlobal: results[2],
254
+ claudeProject: results[3] || { path: '', exists: false, error: '' },
255
+ system: results[4]
256
+ };
257
+ this.promptsPathReferenceCacheKey = cacheKey;
258
+ }).finally(() => {
259
+ this.promptsPathReferenceLoading = false;
260
+ this._promptsPathReferencePromise = null;
261
+ });
262
+ this._promptsPathReferencePromise = promise;
263
+ return promise;
278
264
  },
279
265
 
280
- consumePromptsAxisPendingJump(key) {
281
- const pending = this._promptsAxisPendingJump;
282
- if (!pending || pending.key !== key) {
283
- return;
266
+ promptsPathReferenceOptionList() {
267
+ return buildPromptPathReferenceOptionList({
268
+ currentTab: this.promptsSubTab,
269
+ paths: this.promptsPathReferencePaths || {},
270
+ t: (key, params) => (typeof this.t === 'function' ? this.t(key, params) : key),
271
+ systemFile: (this.sysPromptMode || 'system') === 'append' ? 'APPEND_SYSTEM.md' : 'SYSTEM.md'
272
+ });
273
+ },
274
+
275
+ insertPromptPathReference(entryId, key, event) {
276
+ const select = event && event.target;
277
+ if (select && typeof select.value === 'string') {
278
+ select.value = '';
284
279
  }
285
280
  const meta = this.promptsEditorMeta(key);
286
- if (!meta) {
287
- this._promptsAxisPendingJump = null;
281
+ const entry = (this.promptsPathReferencePaths || {})[entryId];
282
+ if (!meta || !entry || !entry.path || entry.error) {
288
283
  return;
289
284
  }
290
- const axis = this[meta.axisField];
291
- if (!axis || axis.mode !== 'diff' || !axis.ticks.length) {
285
+ if (this[meta.loadingField] || this[meta.savingField] || this[meta.diffVisibleField]) {
292
286
  return;
293
287
  }
294
- this._promptsAxisPendingJump = null;
295
- const target = axis.ticks.find((tick) => tick.startLine === pending.startLine)
296
- || axis.ticks.find((tick) => tick.startLine >= pending.startLine)
297
- || axis.ticks[axis.ticks.length - 1];
298
- const applyScroll = () => {
299
- const view = this.$refs && this.$refs[meta.diffViewRef];
300
- const rows = view && view.children ? Array.prototype.slice.call(view.children) : [];
301
- const row = rows[target.rowIndex];
302
- if (row && typeof row.scrollIntoView === 'function') {
303
- row.scrollIntoView({ block: 'center' });
288
+ const sentence = buildPromptPathReferenceSentence(entry.path);
289
+ if (!sentence) {
290
+ return;
291
+ }
292
+ const textarea = typeof this.$refs === 'object' && this.$refs
293
+ ? this.$refs[meta.textareaRef]
294
+ : null;
295
+ const content = typeof this[meta.contentField] === 'string' ? this[meta.contentField] : '';
296
+ let start = content.length;
297
+ let end = content.length;
298
+ if (textarea && typeof textarea.selectionStart === 'number') {
299
+ start = textarea.selectionStart;
300
+ end = textarea.selectionEnd;
301
+ }
302
+ const result = insertPromptPathReferenceText(content, start, end, sentence);
303
+ this[meta.contentField] = result.content;
304
+ this.schedulePromptsEditorRefresh(key);
305
+ const restoreCaret = () => {
306
+ if (textarea && typeof textarea.setSelectionRange === 'function') {
307
+ textarea.setSelectionRange(result.caret, result.caret);
308
+ textarea.focus();
304
309
  }
305
310
  };
306
311
  if (typeof this.$nextTick === 'function') {
307
- this.$nextTick(applyScroll);
312
+ this.$nextTick(restoreCaret);
308
313
  } else {
309
- applyScroll();
314
+ restoreCaret();
310
315
  }
311
- },
312
-
313
- setPromptsMobileView(view) {
314
- if (view === 'edit' || view === 'preview') {
315
- this.promptsMobileView = view;
316
- }
317
- },
318
-
319
- togglePromptsPreviewCollapsed() {
320
- this.promptsPreviewCollapsed = !this.promptsPreviewCollapsed;
321
- if (typeof this.persistWebUiPreferences === 'function') {
322
- this.persistWebUiPreferences({ promptsPreviewCollapsed: this.promptsPreviewCollapsed });
316
+ if (typeof this.showMessage === 'function') {
317
+ this.showMessage(this.t('prompts.pathReference.toast.inserted'), 'success');
323
318
  }
324
319
  }
325
320
  };
@@ -114,7 +114,10 @@ function normalizeNavigationSnapshot(vm, source = {}) {
114
114
  : currentSkillsTargetApp,
115
115
  promptTemplatesMode: source.promptTemplatesMode === 'manage' || source.promptTemplatesMode === 'compose'
116
116
  ? source.promptTemplatesMode
117
- : currentPromptTemplatesMode
117
+ : currentPromptTemplatesMode,
118
+ pluginsActiveId: typeof source.pluginsActiveId === 'string'
119
+ ? source.pluginsActiveId
120
+ : (typeof vm.pluginsActiveId === 'string' ? vm.pluginsActiveId : '')
118
121
  };
119
122
  }
120
123
 
@@ -475,6 +478,9 @@ export function createWebUiPreferencesMethods(options = {}) {
475
478
  this.settingsTab = this.normalizeSettingsTab(nav.settingsTab);
476
479
  }
477
480
  if (typeof nav.configMode === 'string') this.configMode = nav.configMode;
481
+ if (typeof nav.pluginsActiveId === 'string' && nav.pluginsActiveId.trim()) {
482
+ this.pluginsActiveId = nav.pluginsActiveId.trim();
483
+ }
478
484
  if (typeof nav.mainTab === 'string') {
479
485
  if (typeof this.switchMainTab === 'function') {
480
486
  this.switchMainTab(nav.mainTab);
@@ -232,6 +232,17 @@ const en = Object.freeze({
232
232
  'prompts.subTab.opencode': 'AGENTS.md (OpenCode)',
233
233
  'prompts.subTab.project': 'CLAUDE.md',
234
234
  'prompts.subTab.system': 'System Prompt (Pi)',
235
+ 'prompts.pathReference.title': 'Insert rule reference',
236
+ 'prompts.pathReference.placeholder': 'Insert rule reference…',
237
+ 'prompts.pathReference.codex': 'AGENTS.md (Codex)',
238
+ 'prompts.pathReference.opencode': 'AGENTS.md (OpenCode)',
239
+ 'prompts.pathReference.claudeGlobal': 'CLAUDE.md (Global)',
240
+ 'prompts.pathReference.claudeProject': 'CLAUDE.md (Project)',
241
+ 'prompts.pathReference.system': '{file} (Pi System Prompt)',
242
+ 'prompts.pathReference.projectMissing': 'No project path selected',
243
+ 'prompts.pathReference.notLoaded': 'Loading path…',
244
+ 'prompts.pathReference.loadFailed': 'Failed to load path',
245
+ 'prompts.pathReference.toast.inserted': 'Rule reference inserted',
235
246
  'prompts.subTab.presets': 'Preset pool',
236
247
  'prompts.editor.toolbar': 'Markdown toolbar',
237
248
  'prompts.editor.collapsePreview': 'Collapse preview',
@@ -243,7 +254,6 @@ const en = Object.freeze({
243
254
  'prompts.editor.previewEmpty': 'Nothing to preview yet',
244
255
  'prompts.editor.changeAxis': 'Change navigation axis',
245
256
  'prompts.editor.changeAxisTick': 'Line {line} (+{added} / -{removed})',
246
- 'prompts.editor.changeAxisTruncated': 'Too many changed regions; collapsed to summary',
247
257
  'prompts.presets.title': 'Presets',
248
258
  'prompts.presets.hint': 'Manage presets.',
249
259
  'prompts.presets.namePlaceholder': 'Preset name',
@@ -374,6 +384,17 @@ const en = Object.freeze({
374
384
  'plugins.catalog.promptTemplates.title': 'Prompt templates',
375
385
  'plugins.catalog.promptTemplates.description': 'Standardized, template-driven prompt tools with variables, copy, and export helpers.',
376
386
  'plugins.status.standard': 'standard',
387
+ 'plugins.catalog.textTools.title': 'Text tools',
388
+ 'plugins.catalog.textTools.description': 'A set of lightweight plain-text utilities; the first tool compresses multi-line text into a single line.',
389
+ 'plugins.textTools.title': 'Text tools',
390
+ 'plugins.textTools.tools.aria': 'Text tool selection',
391
+ 'plugins.textTools.tools.compress': 'Compress text',
392
+ 'plugins.textTools.compress.inputLabel': 'Input text',
393
+ 'plugins.textTools.compress.inputPlaceholder': 'Paste or type the text to compress…',
394
+ 'plugins.textTools.compress.inputAria': 'Text to compress',
395
+ 'plugins.textTools.compress.outputTitle': 'Compressed result',
396
+ 'plugins.textTools.compress.outputAria': 'Compressed text result',
397
+ 'plugins.textTools.compress.copy': 'Copy',
377
398
  'plugins.main.ariaWorkspace': 'Plugin workspace',
378
399
  'plugins.refresh': 'Refresh',
379
400
  'plugins.refreshing': 'Refreshing...',
@@ -233,6 +233,17 @@ const ja = Object.freeze({
233
233
  'prompts.subTab.opencode': 'AGENTS.md (OpenCode)',
234
234
  'prompts.subTab.project': 'CLAUDE.md',
235
235
  'prompts.subTab.system': 'System Prompt (Pi)',
236
+ 'prompts.pathReference.title': 'ルール参照を挿入',
237
+ 'prompts.pathReference.placeholder': 'ルール参照を挿入…',
238
+ 'prompts.pathReference.codex': 'AGENTS.md (Codex)',
239
+ 'prompts.pathReference.opencode': 'AGENTS.md (OpenCode)',
240
+ 'prompts.pathReference.claudeGlobal': 'CLAUDE.md(グローバル)',
241
+ 'prompts.pathReference.claudeProject': 'CLAUDE.md(現在のプロジェクト)',
242
+ 'prompts.pathReference.system': '{file}(Pi System Prompt)',
243
+ 'prompts.pathReference.projectMissing': 'プロジェクトパス未選択',
244
+ 'prompts.pathReference.notLoaded': 'パス読み込み中…',
245
+ 'prompts.pathReference.loadFailed': 'パスの読み込みに失敗しました',
246
+ 'prompts.pathReference.toast.inserted': 'ルール参照を挿入しました',
236
247
  'prompts.subTab.presets': 'プリセット',
237
248
  'prompts.editor.toolbar': 'Markdown ツールバー',
238
249
  'prompts.editor.collapsePreview': 'プレビューを折りたたむ',
@@ -244,7 +255,6 @@ const ja = Object.freeze({
244
255
  'prompts.editor.previewEmpty': 'プレビューする内容がありません',
245
256
  'prompts.editor.changeAxis': '変更ナビゲーション軸',
246
257
  'prompts.editor.changeAxisTick': '{line} 行目(+{added} / -{removed})',
247
- 'prompts.editor.changeAxisTruncated': '変更領域が多すぎるため、統計に折りたたまれました',
248
258
  'prompts.presets.title': 'プリセット',
249
259
  'prompts.presets.hint': 'プリセットを管理します。',
250
260
  'prompts.presets.namePlaceholder': 'プリセット名',
@@ -376,6 +386,17 @@ const ja = Object.freeze({
376
386
  'plugins.catalog.promptTemplates.title': 'プロンプトテンプレート',
377
387
  'plugins.catalog.promptTemplates.description': '変数、コピー、エクスポートに対応した標準化テンプレート型プロンプトツール。',
378
388
  'plugins.status.standard': '標準',
389
+ 'plugins.catalog.textTools.title': 'テキストツール',
390
+ 'plugins.catalog.textTools.description': '軽量なテキストユーティリティ集。最初のツールは複数行のテキストを 1 行に圧縮します。',
391
+ 'plugins.textTools.title': 'テキストツール',
392
+ 'plugins.textTools.tools.aria': 'テキストツールの選択',
393
+ 'plugins.textTools.tools.compress': 'テキスト圧縮',
394
+ 'plugins.textTools.compress.inputLabel': '入力テキスト',
395
+ 'plugins.textTools.compress.inputPlaceholder': '圧縮するテキストを貼り付けまたは入力…',
396
+ 'plugins.textTools.compress.inputAria': '圧縮するテキスト',
397
+ 'plugins.textTools.compress.outputTitle': '圧縮結果',
398
+ 'plugins.textTools.compress.outputAria': '圧縮後のテキスト結果',
399
+ 'plugins.textTools.compress.copy': 'コピー',
379
400
  'plugins.main.ariaWorkspace': 'プラグインワークスペース',
380
401
  'plugins.refresh': '更新',
381
402
  'plugins.refreshing': '更新中...',