@earendil-works/pi-coding-agent 0.80.2 → 0.80.5

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 (211) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/README.md +3 -2
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +2 -2
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/config-selector.d.ts +4 -2
  7. package/dist/cli/config-selector.d.ts.map +1 -1
  8. package/dist/cli/config-selector.js +1 -1
  9. package/dist/cli/config-selector.js.map +1 -1
  10. package/dist/cli/file-processor.d.ts.map +1 -1
  11. package/dist/cli/file-processor.js +13 -25
  12. package/dist/cli/file-processor.js.map +1 -1
  13. package/dist/core/agent-session.d.ts +22 -5
  14. package/dist/core/agent-session.d.ts.map +1 -1
  15. package/dist/core/agent-session.js +133 -50
  16. package/dist/core/agent-session.js.map +1 -1
  17. package/dist/core/auth-storage.d.ts.map +1 -1
  18. package/dist/core/auth-storage.js +13 -5
  19. package/dist/core/auth-storage.js.map +1 -1
  20. package/dist/core/cache-stats.d.ts +49 -0
  21. package/dist/core/cache-stats.d.ts.map +1 -0
  22. package/dist/core/cache-stats.js +101 -0
  23. package/dist/core/cache-stats.js.map +1 -0
  24. package/dist/core/compaction/compaction.d.ts +1 -2
  25. package/dist/core/compaction/compaction.d.ts.map +1 -1
  26. package/dist/core/compaction/compaction.js +56 -81
  27. package/dist/core/compaction/compaction.js.map +1 -1
  28. package/dist/core/extensions/index.d.ts +1 -1
  29. package/dist/core/extensions/index.d.ts.map +1 -1
  30. package/dist/core/extensions/index.js.map +1 -1
  31. package/dist/core/extensions/loader.d.ts.map +1 -1
  32. package/dist/core/extensions/loader.js +10 -0
  33. package/dist/core/extensions/loader.js.map +1 -1
  34. package/dist/core/extensions/runner.d.ts +5 -3
  35. package/dist/core/extensions/runner.d.ts.map +1 -1
  36. package/dist/core/extensions/runner.js +38 -0
  37. package/dist/core/extensions/runner.js.map +1 -1
  38. package/dist/core/extensions/types.d.ts +39 -5
  39. package/dist/core/extensions/types.d.ts.map +1 -1
  40. package/dist/core/extensions/types.js.map +1 -1
  41. package/dist/core/http-dispatcher.d.ts.map +1 -1
  42. package/dist/core/http-dispatcher.js +28 -1
  43. package/dist/core/http-dispatcher.js.map +1 -1
  44. package/dist/core/index.d.ts +1 -1
  45. package/dist/core/index.d.ts.map +1 -1
  46. package/dist/core/index.js.map +1 -1
  47. package/dist/core/keybindings.d.ts.map +1 -1
  48. package/dist/core/keybindings.js +3 -6
  49. package/dist/core/keybindings.js.map +1 -1
  50. package/dist/core/model-registry.d.ts +3 -0
  51. package/dist/core/model-registry.d.ts.map +1 -1
  52. package/dist/core/model-registry.js +16 -2
  53. package/dist/core/model-registry.js.map +1 -1
  54. package/dist/core/model-resolver.d.ts +10 -0
  55. package/dist/core/model-resolver.d.ts.map +1 -1
  56. package/dist/core/model-resolver.js +15 -18
  57. package/dist/core/model-resolver.js.map +1 -1
  58. package/dist/core/package-manager.d.ts +4 -1
  59. package/dist/core/package-manager.d.ts.map +1 -1
  60. package/dist/core/package-manager.js +68 -26
  61. package/dist/core/package-manager.js.map +1 -1
  62. package/dist/core/provider-attribution.d.ts.map +1 -1
  63. package/dist/core/provider-attribution.js +0 -10
  64. package/dist/core/provider-attribution.js.map +1 -1
  65. package/dist/core/resource-loader.d.ts +2 -2
  66. package/dist/core/resource-loader.d.ts.map +1 -1
  67. package/dist/core/resource-loader.js +8 -7
  68. package/dist/core/resource-loader.js.map +1 -1
  69. package/dist/core/sdk.d.ts +1 -1
  70. package/dist/core/sdk.d.ts.map +1 -1
  71. package/dist/core/sdk.js +8 -1
  72. package/dist/core/sdk.js.map +1 -1
  73. package/dist/core/session-manager.d.ts +21 -2
  74. package/dist/core/session-manager.d.ts.map +1 -1
  75. package/dist/core/session-manager.js +104 -72
  76. package/dist/core/session-manager.js.map +1 -1
  77. package/dist/core/settings-manager.d.ts +10 -0
  78. package/dist/core/settings-manager.d.ts.map +1 -1
  79. package/dist/core/settings-manager.js +27 -0
  80. package/dist/core/settings-manager.js.map +1 -1
  81. package/dist/core/slash-commands.d.ts +1 -0
  82. package/dist/core/slash-commands.d.ts.map +1 -1
  83. package/dist/core/slash-commands.js +3 -3
  84. package/dist/core/slash-commands.js.map +1 -1
  85. package/dist/core/timings.d.ts +4 -2
  86. package/dist/core/timings.d.ts.map +1 -1
  87. package/dist/core/timings.js +24 -14
  88. package/dist/core/timings.js.map +1 -1
  89. package/dist/core/tools/bash.d.ts.map +1 -1
  90. package/dist/core/tools/bash.js +20 -5
  91. package/dist/core/tools/bash.js.map +1 -1
  92. package/dist/core/tools/edit.d.ts.map +1 -1
  93. package/dist/core/tools/edit.js +2 -2
  94. package/dist/core/tools/edit.js.map +1 -1
  95. package/dist/core/tools/read.d.ts.map +1 -1
  96. package/dist/core/tools/read.js +12 -25
  97. package/dist/core/tools/read.js.map +1 -1
  98. package/dist/index.d.ts +3 -2
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +2 -1
  101. package/dist/index.js.map +1 -1
  102. package/dist/main.d.ts +2 -2
  103. package/dist/main.d.ts.map +1 -1
  104. package/dist/main.js +17 -4
  105. package/dist/main.js.map +1 -1
  106. package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  107. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  108. package/dist/modes/interactive/components/assistant-message.js +23 -15
  109. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  110. package/dist/modes/interactive/components/config-selector.d.ts +34 -3
  111. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  112. package/dist/modes/interactive/components/config-selector.js +280 -25
  113. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  114. package/dist/modes/interactive/components/custom-entry.d.ts +19 -0
  115. package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -0
  116. package/dist/modes/interactive/components/custom-entry.js +52 -0
  117. package/dist/modes/interactive/components/custom-entry.js.map +1 -0
  118. package/dist/modes/interactive/components/extension-editor.d.ts +3 -1
  119. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  120. package/dist/modes/interactive/components/extension-editor.js +12 -3
  121. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  122. package/dist/modes/interactive/components/footer.d.ts +4 -0
  123. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  124. package/dist/modes/interactive/components/footer.js +1 -1
  125. package/dist/modes/interactive/components/footer.js.map +1 -1
  126. package/dist/modes/interactive/components/oauth-selector.d.ts +3 -1
  127. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  128. package/dist/modes/interactive/components/oauth-selector.js +17 -6
  129. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  130. package/dist/modes/interactive/components/settings-selector.d.ts +4 -0
  131. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  132. package/dist/modes/interactive/components/settings-selector.js +23 -1
  133. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  134. package/dist/modes/interactive/components/status-indicator.d.ts +28 -0
  135. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -0
  136. package/dist/modes/interactive/components/status-indicator.js +60 -0
  137. package/dist/modes/interactive/components/status-indicator.js.map +1 -0
  138. package/dist/modes/interactive/components/user-message.d.ts +6 -2
  139. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  140. package/dist/modes/interactive/components/user-message.js +19 -6
  141. package/dist/modes/interactive/components/user-message.js.map +1 -1
  142. package/dist/modes/interactive/interactive-mode.d.ts +21 -10
  143. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  144. package/dist/modes/interactive/interactive-mode.js +417 -196
  145. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  146. package/dist/modes/print-mode.d.ts.map +1 -1
  147. package/dist/modes/print-mode.js +1 -1
  148. package/dist/modes/print-mode.js.map +1 -1
  149. package/dist/modes/rpc/rpc-client.d.ts +21 -6
  150. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  151. package/dist/modes/rpc/rpc-client.js +17 -3
  152. package/dist/modes/rpc/rpc-client.js.map +1 -1
  153. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  154. package/dist/modes/rpc/rpc-mode.js +20 -1
  155. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  156. package/dist/modes/rpc/rpc-types.d.ts +26 -0
  157. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  158. package/dist/modes/rpc/rpc-types.js.map +1 -1
  159. package/dist/package-manager-cli.d.ts +2 -2
  160. package/dist/package-manager-cli.d.ts.map +1 -1
  161. package/dist/package-manager-cli.js +71 -17
  162. package/dist/package-manager-cli.js.map +1 -1
  163. package/dist/rpc-entry.d.ts +3 -0
  164. package/dist/rpc-entry.d.ts.map +1 -0
  165. package/dist/rpc-entry.js +10 -0
  166. package/dist/rpc-entry.js.map +1 -0
  167. package/dist/utils/clipboard-image.d.ts.map +1 -1
  168. package/dist/utils/clipboard-image.js +1 -1
  169. package/dist/utils/clipboard-image.js.map +1 -1
  170. package/dist/utils/image-convert.d.ts +1 -0
  171. package/dist/utils/image-convert.d.ts.map +1 -1
  172. package/dist/utils/image-convert.js +21 -15
  173. package/dist/utils/image-convert.js.map +1 -1
  174. package/dist/utils/image-process.d.ts +18 -0
  175. package/dist/utils/image-process.d.ts.map +1 -0
  176. package/dist/utils/image-process.js +83 -0
  177. package/dist/utils/image-process.js.map +1 -0
  178. package/dist/utils/mime.d.ts.map +1 -1
  179. package/dist/utils/mime.js +41 -0
  180. package/dist/utils/mime.js.map +1 -1
  181. package/docs/extensions.md +88 -17
  182. package/docs/keybindings.md +1 -1
  183. package/docs/models.md +4 -4
  184. package/docs/packages.md +2 -2
  185. package/docs/providers.md +2 -1
  186. package/docs/rpc.md +71 -3
  187. package/docs/sdk.md +50 -0
  188. package/docs/session-format.md +18 -8
  189. package/docs/settings.md +11 -0
  190. package/docs/usage.md +3 -3
  191. package/examples/extensions/README.md +1 -0
  192. package/examples/extensions/custom-compaction.ts +1 -0
  193. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  194. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  195. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  196. package/examples/extensions/entry-renderer.ts +41 -0
  197. package/examples/extensions/gondolin/package-lock.json +2 -2
  198. package/examples/extensions/gondolin/package.json +1 -1
  199. package/examples/extensions/handoff.ts +1 -1
  200. package/examples/extensions/qna.ts +1 -1
  201. package/examples/extensions/question.ts +1 -0
  202. package/examples/extensions/reload-runtime.ts +2 -2
  203. package/examples/extensions/sandbox/package-lock.json +2 -2
  204. package/examples/extensions/sandbox/package.json +1 -1
  205. package/examples/extensions/summarize.ts +1 -0
  206. package/examples/extensions/with-deps/package-lock.json +2 -2
  207. package/examples/extensions/with-deps/package.json +1 -1
  208. package/examples/sdk/12-full-control.ts +1 -1
  209. package/examples/sdk/README.md +1 -1
  210. package/npm-shrinkwrap.json +12 -12
  211. package/package.json +8 -5
@@ -5,9 +5,11 @@ import { homedir } from "node:os";
5
5
  import { basename, dirname, join, relative } from "node:path";
6
6
  import { Container, getKeybindings, Input, matchesKey, Spacer, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
7
7
  import { CONFIG_DIR_NAME } from "../../../config.js";
8
+ import { canonicalizePath, isLocalPath, resolvePath } from "../../../utils/paths.js";
8
9
  import { theme } from "../theme/theme.js";
9
10
  import { DynamicBorder } from "./dynamic-border.js";
10
- import { rawKeyHint } from "./keybinding-hints.js";
11
+ import { keyHint, rawKeyHint } from "./keybinding-hints.js";
12
+ const RESOURCE_TYPES = ["extensions", "skills", "prompts", "themes"];
11
13
  const RESOURCE_TYPE_LABELS = {
12
14
  extensions: "Extensions",
13
15
  skills: "Skills",
@@ -121,22 +123,34 @@ function buildGroups(resolved, agentDir) {
121
123
  return groups;
122
124
  }
123
125
  class ConfigSelectorHeader {
126
+ writeScope;
127
+ projectModeAvailable;
128
+ constructor(writeScope, projectModeAvailable) {
129
+ this.writeScope = writeScope;
130
+ this.projectModeAvailable = projectModeAvailable;
131
+ }
132
+ setWriteScope(writeScope) {
133
+ this.writeScope = writeScope;
134
+ }
124
135
  invalidate() { }
125
136
  render(width) {
126
- const title = theme.bold("Resource Configuration");
137
+ const title = theme.bold(this.writeScope === "project" ? "Project Local Resources" : "Global Resources");
127
138
  const sep = theme.fg("muted", " · ");
128
- const hint = rawKeyHint("space", "toggle") + sep + rawKeyHint("esc", "close");
129
- const hintWidth = visibleWidth(hint);
130
- const titleWidth = visibleWidth(title);
131
- const spacing = Math.max(1, width - titleWidth - hintWidth);
139
+ const switchHint = this.projectModeAvailable ? keyHint("tui.input.tab", "switch mode") + sep : "";
140
+ const actionHint = this.writeScope === "project" ? rawKeyHint("space", "cycle inherit/+/-") : rawKeyHint("space", "toggle");
141
+ const hint = switchHint + actionHint + sep + rawKeyHint("esc", "close");
142
+ const spacing = Math.max(1, width - visibleWidth(title) - visibleWidth(hint));
143
+ const scopeHint = this.writeScope === "project"
144
+ ? theme.fg("muted", `${CONFIG_DIR_NAME}/settings.json · inherited global resources are dimmed`)
145
+ : theme.fg("muted", `~/${CONFIG_DIR_NAME}/agent/settings.json`);
132
146
  return [
133
147
  truncateToWidth(`${title}${" ".repeat(spacing)}${hint}`, width, ""),
134
- theme.fg("muted", "Type to filter resources"),
148
+ truncateToWidth(scopeHint, width, ""),
135
149
  ];
136
150
  }
137
151
  }
138
152
  class ResourceList {
139
- groups;
153
+ groupsByScope;
140
154
  flatItems = [];
141
155
  filteredItems = [];
142
156
  selectedIndex = 0;
@@ -145,9 +159,12 @@ class ResourceList {
145
159
  settingsManager;
146
160
  cwd;
147
161
  agentDir;
162
+ writeScope;
163
+ inheritedEnabledByKey;
148
164
  onCancel;
149
165
  onExit;
150
166
  onToggle;
167
+ onSwitchMode;
151
168
  _focused = false;
152
169
  get focused() {
153
170
  return this._focused;
@@ -156,11 +173,13 @@ class ResourceList {
156
173
  this._focused = value;
157
174
  this.searchInput.focused = value;
158
175
  }
159
- constructor(groups, settingsManager, cwd, agentDir, terminalHeight) {
160
- this.groups = groups;
176
+ constructor(groupsByScope, settingsManager, cwd, agentDir, terminalHeight, writeScope = "global") {
177
+ this.groupsByScope = groupsByScope;
161
178
  this.settingsManager = settingsManager;
162
179
  this.cwd = cwd;
163
180
  this.agentDir = agentDir;
181
+ this.writeScope = writeScope;
182
+ this.inheritedEnabledByKey = this.buildInheritedEnabledMap(groupsByScope.global);
164
183
  this.searchInput = new Input();
165
184
  // 8 lines of chrome: top spacer + top border + spacer + header (2 lines) + spacer + bottom spacer + bottom border
166
185
  const chrome = 8;
@@ -168,6 +187,25 @@ class ResourceList {
168
187
  this.buildFlatList();
169
188
  this.filteredItems = [...this.flatItems];
170
189
  }
190
+ setWriteScope(writeScope) {
191
+ this.writeScope = writeScope;
192
+ this.buildFlatList();
193
+ this.filterItems(this.searchInput.getValue());
194
+ }
195
+ get groups() {
196
+ return this.groupsByScope[this.writeScope];
197
+ }
198
+ buildInheritedEnabledMap(groups) {
199
+ const result = new Map();
200
+ for (const group of groups) {
201
+ for (const subgroup of group.subgroups) {
202
+ for (const item of subgroup.items) {
203
+ result.set(this.getResourceItemKey(item), item.enabled);
204
+ }
205
+ }
206
+ }
207
+ return result;
208
+ }
171
209
  buildFlatList() {
172
210
  this.flatItems = [];
173
211
  for (const group of this.groups) {
@@ -274,21 +312,25 @@ class ResourceList {
274
312
  const isSelected = i === this.selectedIndex;
275
313
  if (entry.type === "group") {
276
314
  // Main group header (no cursor)
277
- const groupLine = theme.fg("accent", theme.bold(entry.group.label));
315
+ const inherited = this.writeScope === "project" && entry.group.scope === "user";
316
+ const label = theme.bold(`${entry.group.label}${inherited ? " · inherited global" : ""}`);
317
+ const groupLine = theme.fg(inherited ? "dim" : "accent", label);
278
318
  lines.push(truncateToWidth(` ${groupLine}`, width, ""));
279
319
  }
280
320
  else if (entry.type === "subgroup") {
281
321
  // Subgroup header (indented, no cursor)
282
- const subgroupLine = theme.fg("muted", entry.subgroup.label);
322
+ const color = this.writeScope === "project" && entry.group.scope === "user" ? "dim" : "muted";
323
+ const subgroupLine = theme.fg(color, entry.subgroup.label);
283
324
  lines.push(truncateToWidth(` ${subgroupLine}`, width, ""));
284
325
  }
285
326
  else {
286
327
  // Resource item (cursor only on items)
287
328
  const item = entry.item;
288
329
  const cursor = isSelected ? "> " : " ";
289
- const checkbox = item.enabled ? theme.fg("success", "[x]") : theme.fg("dim", "[ ]");
290
- const name = isSelected ? theme.bold(item.displayName) : item.displayName;
291
- lines.push(truncateToWidth(`${cursor} ${checkbox} ${name}`, width, "..."));
330
+ const dimmed = this.isDimmedItem(item);
331
+ const nameText = isSelected && !dimmed ? theme.bold(item.displayName) : item.displayName;
332
+ const name = dimmed ? theme.fg("dim", nameText) : nameText;
333
+ lines.push(truncateToWidth(`${cursor} ${this.renderCheckbox(item)} ${name}${this.getItemSuffix(item)}`, width, "..."));
292
334
  }
293
335
  }
294
336
  // Scroll indicator
@@ -339,13 +381,18 @@ class ResourceList {
339
381
  this.onExit?.();
340
382
  return;
341
383
  }
384
+ if (kb.matches(data, "tui.input.tab")) {
385
+ this.onSwitchMode?.();
386
+ return;
387
+ }
342
388
  if (data === " " || kb.matches(data, "tui.select.confirm")) {
343
389
  const entry = this.filteredItems[this.selectedIndex];
344
- if (entry?.type === "item") {
345
- const newEnabled = !entry.item.enabled;
346
- this.toggleResource(entry.item, newEnabled);
347
- this.updateItem(entry.item, newEnabled);
348
- this.onToggle?.(entry.item, newEnabled);
390
+ if (entry?.type === "item" && (this.writeScope === "project" || this.getItemScope(entry.item) === "user")) {
391
+ const newEnabled = this.toggleResource(entry.item);
392
+ if (newEnabled !== undefined) {
393
+ this.updateItem(entry.item, newEnabled);
394
+ this.onToggle?.(entry.item, newEnabled);
395
+ }
349
396
  }
350
397
  return;
351
398
  }
@@ -353,13 +400,21 @@ class ResourceList {
353
400
  this.searchInput.handleInput(data);
354
401
  this.filterItems(this.searchInput.getValue());
355
402
  }
356
- toggleResource(item, enabled) {
403
+ toggleResource(item) {
404
+ if (this.writeScope === "project") {
405
+ const state = this.getNextOverrideState(item);
406
+ if (!this.setProjectResourceOverride(item, state))
407
+ return undefined;
408
+ return state === "inherit" ? this.getInheritedEnabled(item) : state === "load";
409
+ }
410
+ const enabled = !item.enabled;
357
411
  if (item.metadata.origin === "top-level") {
358
412
  this.toggleTopLevelResource(item, enabled);
359
413
  }
360
414
  else {
361
415
  this.togglePackageResource(item, enabled);
362
416
  }
417
+ return enabled;
363
418
  }
364
419
  toggleTopLevelResource(item, enabled) {
365
420
  const scope = item.metadata.scope;
@@ -457,6 +512,188 @@ class ResourceList {
457
512
  this.settingsManager.setPackages(packages);
458
513
  }
459
514
  }
515
+ renderCheckbox(item) {
516
+ if (this.writeScope === "project") {
517
+ const state = this.getProjectOverrideState(item);
518
+ if (state === "load")
519
+ return theme.fg("success", "[+]");
520
+ if (state === "unload")
521
+ return theme.fg("warning", "[-]");
522
+ return theme.fg("dim", item.enabled ? "[x]" : "[ ]");
523
+ }
524
+ return item.enabled ? theme.fg("success", "[x]") : theme.fg("dim", "[ ]");
525
+ }
526
+ getItemSuffix(item) {
527
+ if (this.writeScope !== "project")
528
+ return "";
529
+ const state = this.getProjectOverrideState(item);
530
+ if (state === "load")
531
+ return theme.fg("muted", " project load");
532
+ if (state === "unload")
533
+ return theme.fg("muted", " project unload");
534
+ return this.isInheritedGlobalItem(item) ? theme.fg("dim", " inherited global") : "";
535
+ }
536
+ isDimmedItem(item) {
537
+ return (this.writeScope === "project" &&
538
+ this.isInheritedGlobalItem(item) &&
539
+ this.getProjectOverrideState(item) === "inherit");
540
+ }
541
+ setProjectResourceOverride(item, state) {
542
+ return item.metadata.origin === "top-level"
543
+ ? this.setProjectTopLevelOverride(item, state)
544
+ : this.setProjectPackageOverride(item, state);
545
+ }
546
+ setProjectTopLevelOverride(item, state) {
547
+ const current = (this.settingsManager.getProjectSettings()[item.resourceType] ?? []);
548
+ const pattern = this.isInheritedGlobalItem(item) ? item.path : this.getResourcePatternForScope(item, "project");
549
+ const patterns = this.getTopLevelOverridePatterns(item, "project");
550
+ const updated = current.filter((entry) => {
551
+ const target = this.getPatternEntryTarget(entry);
552
+ if ((entry.startsWith("!") || entry.startsWith("+") || entry.startsWith("-")) && patterns.has(target))
553
+ return false;
554
+ return !(state === "inherit" && this.isInheritedGlobalItem(item) && target === pattern);
555
+ });
556
+ if (state !== "inherit") {
557
+ if (this.isInheritedGlobalItem(item) && !updated.includes(pattern))
558
+ updated.push(pattern);
559
+ updated.push(`${state === "load" ? "+" : "-"}${pattern}`);
560
+ }
561
+ this.setProjectTopLevelPaths(item.resourceType, updated);
562
+ return true;
563
+ }
564
+ setProjectTopLevelPaths(key, paths) {
565
+ if (key === "extensions")
566
+ this.settingsManager.setProjectExtensionPaths(paths);
567
+ else if (key === "skills")
568
+ this.settingsManager.setProjectSkillPaths(paths);
569
+ else if (key === "prompts")
570
+ this.settingsManager.setProjectPromptTemplatePaths(paths);
571
+ else
572
+ this.settingsManager.setProjectThemePaths(paths);
573
+ }
574
+ setProjectPackageOverride(item, state) {
575
+ const packages = [...(this.settingsManager.getProjectSettings().packages ?? [])];
576
+ let pkgIndex = packages.findIndex((pkg) => this.packageSourceStringMatches(item.metadata.source, this.getItemScope(item), typeof pkg === "string" ? pkg : pkg.source, "project"));
577
+ if (pkgIndex === -1) {
578
+ if (state === "inherit")
579
+ return false;
580
+ packages.push(this.createPackageOverrideSource(item));
581
+ pkgIndex = packages.length - 1;
582
+ }
583
+ let pkg = packages[pkgIndex];
584
+ if (pkg === undefined)
585
+ return false;
586
+ if (typeof pkg === "string") {
587
+ pkg = { source: pkg };
588
+ packages[pkgIndex] = pkg;
589
+ }
590
+ const pattern = this.getPackageResourcePattern(item);
591
+ const updated = (pkg[item.resourceType] ?? []).filter((entry) => this.getPatternEntryTarget(entry) !== pattern);
592
+ if (state !== "inherit")
593
+ updated.push(`${state === "load" ? "+" : "-"}${pattern}`);
594
+ pkg[item.resourceType] = updated.length > 0 ? updated : undefined;
595
+ if (!RESOURCE_TYPES.some((key) => pkg[key] !== undefined)) {
596
+ if (pkg.autoload === false)
597
+ packages.splice(pkgIndex, 1);
598
+ else
599
+ packages[pkgIndex] = pkg.source;
600
+ }
601
+ this.settingsManager.setProjectPackages(packages);
602
+ return true;
603
+ }
604
+ getNextOverrideState(item) {
605
+ const state = this.getProjectOverrideState(item);
606
+ const inheritedEnabled = this.getInheritedEnabled(item);
607
+ if (state === "inherit")
608
+ return inheritedEnabled ? "unload" : "load";
609
+ if (state === "unload")
610
+ return inheritedEnabled ? "load" : "inherit";
611
+ return inheritedEnabled ? "inherit" : "unload";
612
+ }
613
+ getProjectOverrideState(item) {
614
+ if (this.writeScope !== "project")
615
+ return "inherit";
616
+ if (item.metadata.origin === "top-level") {
617
+ return this.getOverrideStateFromEntries((this.settingsManager.getProjectSettings()[item.resourceType] ?? []), this.getTopLevelOverridePatterns(item, "project"), false);
618
+ }
619
+ const pkg = this.findMatchingPackageSource(item, "project");
620
+ if (typeof pkg !== "object")
621
+ return "inherit";
622
+ const entries = pkg[item.resourceType];
623
+ if (entries === undefined)
624
+ return "inherit";
625
+ return this.getOverrideStateFromEntries(entries, new Set([this.getPackageResourcePattern(item)]), pkg.autoload !== false);
626
+ }
627
+ getOverrideStateFromEntries(entries, patterns, emptyArrayIsUnload) {
628
+ if (entries.length === 0 && emptyArrayIsUnload)
629
+ return "unload";
630
+ let state = "inherit";
631
+ for (const entry of entries) {
632
+ if (!patterns.has(this.getPatternEntryTarget(entry)))
633
+ continue;
634
+ if (entry.startsWith("!") || entry.startsWith("-"))
635
+ state = "unload";
636
+ else
637
+ state = "load";
638
+ }
639
+ return state;
640
+ }
641
+ getInheritedEnabled(item) {
642
+ return (this.inheritedEnabledByKey.get(this.getResourceItemKey(item)) ??
643
+ (this.getItemScope(item) === "user" ? item.enabled : true));
644
+ }
645
+ isInheritedGlobalItem(item) {
646
+ return this.getItemScope(item) === "user" || this.inheritedEnabledByKey.has(this.getResourceItemKey(item));
647
+ }
648
+ getTopLevelOverridePatterns(item, scope) {
649
+ const baseDir = this.getTopLevelBaseDir(scope);
650
+ const patterns = new Set([
651
+ this.getResourcePatternForScope(item, scope),
652
+ item.path,
653
+ relative(baseDir, item.path),
654
+ ]);
655
+ if (item.metadata.baseDir)
656
+ patterns.add(relative(item.metadata.baseDir, item.path));
657
+ return patterns;
658
+ }
659
+ getResourcePatternForScope(item, scope) {
660
+ const sourceScope = this.getItemScope(item);
661
+ if (scope !== sourceScope)
662
+ return item.path;
663
+ const baseDir = item.metadata.baseDir ?? this.getTopLevelBaseDir(sourceScope);
664
+ return relative(baseDir, item.path);
665
+ }
666
+ createPackageOverrideSource(item) {
667
+ const source = item.metadata.source;
668
+ if (!isLocalPath(source))
669
+ return { source, autoload: false };
670
+ const sourcePath = resolvePath(source, this.getTopLevelBaseDir(this.getItemScope(item)), { trim: true });
671
+ return { source: relative(this.getTopLevelBaseDir("project"), sourcePath) || ".", autoload: false };
672
+ }
673
+ packageSourceStringMatches(leftSource, leftScope, rightSource, rightScope) {
674
+ if (leftSource === rightSource)
675
+ return true;
676
+ if (!isLocalPath(leftSource) || !isLocalPath(rightSource))
677
+ return false;
678
+ const left = resolvePath(leftSource, this.getTopLevelBaseDir(leftScope), { trim: true });
679
+ const right = resolvePath(rightSource, this.getTopLevelBaseDir(rightScope), { trim: true });
680
+ return left === right;
681
+ }
682
+ findMatchingPackageSource(item, targetScope) {
683
+ const settings = targetScope === "project"
684
+ ? this.settingsManager.getProjectSettings()
685
+ : this.settingsManager.getGlobalSettings();
686
+ return (settings.packages ?? []).find((pkg) => this.packageSourceStringMatches(item.metadata.source, this.getItemScope(item), typeof pkg === "string" ? pkg : pkg.source, targetScope));
687
+ }
688
+ getPatternEntryTarget(entry) {
689
+ return entry.startsWith("!") || entry.startsWith("+") || entry.startsWith("-") ? entry.slice(1) : entry;
690
+ }
691
+ getResourceItemKey(item) {
692
+ return `${item.resourceType}:${canonicalizePath(item.path)}`;
693
+ }
694
+ getItemScope(item) {
695
+ return item.metadata.scope === "project" ? "project" : "user";
696
+ }
460
697
  getTopLevelBaseDir(scope) {
461
698
  return scope === "project" ? join(this.cwd, CONFIG_DIR_NAME) : this.agentDir;
462
699
  }
@@ -471,7 +708,9 @@ class ResourceList {
471
708
  }
472
709
  }
473
710
  export class ConfigSelectorComponent extends Container {
711
+ header;
474
712
  resourceList;
713
+ writeScope;
475
714
  _focused = false;
476
715
  get focused() {
477
716
  return this._focused;
@@ -480,25 +719,41 @@ export class ConfigSelectorComponent extends Container {
480
719
  this._focused = value;
481
720
  this.resourceList.focused = value;
482
721
  }
483
- constructor(resolvedPaths, settingsManager, cwd, agentDir, onClose, onExit, requestRender, terminalHeight) {
722
+ constructor(resolvedPaths, settingsManager, cwd, agentDir, onClose, onExit, requestRender, terminalHeight, writeScope = "global", projectModeAvailable = true) {
484
723
  super();
485
- const groups = buildGroups(resolvedPaths, agentDir);
724
+ this.writeScope = writeScope;
725
+ const groupsByScope = {
726
+ global: buildGroups(resolvedPaths.global, agentDir),
727
+ project: buildGroups(resolvedPaths.project, agentDir),
728
+ };
486
729
  // Add header
487
730
  this.addChild(new Spacer(1));
488
731
  this.addChild(new DynamicBorder());
489
732
  this.addChild(new Spacer(1));
490
- this.addChild(new ConfigSelectorHeader());
733
+ this.header = new ConfigSelectorHeader(this.writeScope, projectModeAvailable);
734
+ this.addChild(this.header);
491
735
  this.addChild(new Spacer(1));
492
736
  // Resource list
493
- this.resourceList = new ResourceList(groups, settingsManager, cwd, agentDir, terminalHeight);
737
+ this.resourceList = new ResourceList(groupsByScope, settingsManager, cwd, agentDir, terminalHeight, this.writeScope);
494
738
  this.resourceList.onCancel = onClose;
495
739
  this.resourceList.onExit = onExit;
496
740
  this.resourceList.onToggle = () => requestRender();
741
+ if (projectModeAvailable) {
742
+ this.resourceList.onSwitchMode = () => {
743
+ this.switchWriteScope();
744
+ requestRender();
745
+ };
746
+ }
497
747
  this.addChild(this.resourceList);
498
748
  // Bottom border
499
749
  this.addChild(new Spacer(1));
500
750
  this.addChild(new DynamicBorder());
501
751
  }
752
+ switchWriteScope() {
753
+ this.writeScope = this.writeScope === "global" ? "project" : "global";
754
+ this.header.setWriteScope(this.writeScope);
755
+ this.resourceList.setWriteScope(this.writeScope);
756
+ }
502
757
  getResourceList() {
503
758
  return this.resourceList;
504
759
  }