@code-yeongyu/senpi 2026.7.9 → 2026.7.10

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 (207) hide show
  1. package/CHANGELOG.md +37 -1
  2. package/README.md +1 -1
  3. package/dist/cli/args.js +3 -3
  4. package/dist/cli/args.js.map +1 -1
  5. package/dist/cli/config-selector.d.ts +4 -2
  6. package/dist/cli/config-selector.d.ts.map +1 -1
  7. package/dist/cli/config-selector.js +1 -1
  8. package/dist/cli/config-selector.js.map +1 -1
  9. package/dist/core/agent-session.d.ts +18 -3
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +153 -36
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/cache-stats.d.ts +49 -0
  14. package/dist/core/cache-stats.d.ts.map +1 -0
  15. package/dist/core/cache-stats.js +101 -0
  16. package/dist/core/cache-stats.js.map +1 -0
  17. package/dist/core/compaction/compaction.d.ts +1 -2
  18. package/dist/core/compaction/compaction.d.ts.map +1 -1
  19. package/dist/core/compaction/compaction.js +54 -79
  20. package/dist/core/compaction/compaction.js.map +1 -1
  21. package/dist/core/extensions/builtin/goal/continuation.js +1 -1
  22. package/dist/core/extensions/builtin/goal/continuation.js.map +1 -1
  23. package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
  24. package/dist/core/extensions/builtin/goal/index.js +1 -0
  25. package/dist/core/extensions/builtin/goal/index.js.map +1 -1
  26. package/dist/core/extensions/builtin/import-repro.d.ts.map +1 -1
  27. package/dist/core/extensions/builtin/import-repro.js +100 -6
  28. package/dist/core/extensions/builtin/import-repro.js.map +1 -1
  29. package/dist/core/extensions/builtin/mcp/connection.d.ts +1 -0
  30. package/dist/core/extensions/builtin/mcp/connection.d.ts.map +1 -1
  31. package/dist/core/extensions/builtin/mcp/connection.js +15 -0
  32. package/dist/core/extensions/builtin/mcp/connection.js.map +1 -1
  33. package/dist/core/extensions/builtin/mcp/service.d.ts.map +1 -1
  34. package/dist/core/extensions/builtin/mcp/service.js +3 -1
  35. package/dist/core/extensions/builtin/mcp/service.js.map +1 -1
  36. package/dist/core/extensions/index.d.ts +1 -1
  37. package/dist/core/extensions/index.d.ts.map +1 -1
  38. package/dist/core/extensions/index.js.map +1 -1
  39. package/dist/core/extensions/runner.d.ts +4 -3
  40. package/dist/core/extensions/runner.d.ts.map +1 -1
  41. package/dist/core/extensions/runner.js +30 -24
  42. package/dist/core/extensions/runner.js.map +1 -1
  43. package/dist/core/extensions/types.d.ts +23 -3
  44. package/dist/core/extensions/types.d.ts.map +1 -1
  45. package/dist/core/extensions/types.js.map +1 -1
  46. package/dist/core/model-registry.d.ts +3 -0
  47. package/dist/core/model-registry.d.ts.map +1 -1
  48. package/dist/core/model-registry.js +20 -3
  49. package/dist/core/model-registry.js.map +1 -1
  50. package/dist/core/package-manager.d.ts +4 -1
  51. package/dist/core/package-manager.d.ts.map +1 -1
  52. package/dist/core/package-manager.js +68 -26
  53. package/dist/core/package-manager.js.map +1 -1
  54. package/dist/core/resource-loader.d.ts +2 -2
  55. package/dist/core/resource-loader.d.ts.map +1 -1
  56. package/dist/core/resource-loader.js +6 -7
  57. package/dist/core/resource-loader.js.map +1 -1
  58. package/dist/core/sdk.d.ts +1 -1
  59. package/dist/core/sdk.d.ts.map +1 -1
  60. package/dist/core/sdk.js +9 -2
  61. package/dist/core/sdk.js.map +1 -1
  62. package/dist/core/session-manager.d.ts.map +1 -1
  63. package/dist/core/session-manager.js +11 -2
  64. package/dist/core/session-manager.js.map +1 -1
  65. package/dist/core/settings-manager.d.ts +5 -0
  66. package/dist/core/settings-manager.d.ts.map +1 -1
  67. package/dist/core/settings-manager.js +8 -0
  68. package/dist/core/settings-manager.js.map +1 -1
  69. package/dist/core/slash-commands.d.ts +1 -0
  70. package/dist/core/slash-commands.d.ts.map +1 -1
  71. package/dist/core/slash-commands.js +3 -3
  72. package/dist/core/slash-commands.js.map +1 -1
  73. package/dist/index.d.ts +1 -1
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js.map +1 -1
  76. package/dist/main.d.ts +2 -2
  77. package/dist/main.d.ts.map +1 -1
  78. package/dist/main.js +1 -0
  79. package/dist/main.js.map +1 -1
  80. package/dist/modes/interactive/components/config-selector.d.ts +34 -3
  81. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  82. package/dist/modes/interactive/components/config-selector.js +272 -24
  83. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  84. package/dist/modes/interactive/components/footer.d.ts +4 -0
  85. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  86. package/dist/modes/interactive/components/footer.js +5 -2
  87. package/dist/modes/interactive/components/footer.js.map +1 -1
  88. package/dist/modes/interactive/components/oauth-selector.d.ts +3 -1
  89. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  90. package/dist/modes/interactive/components/oauth-selector.js +16 -6
  91. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  92. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  93. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  94. package/dist/modes/interactive/components/settings-selector.js +10 -0
  95. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  96. package/dist/modes/interactive/interactive-mode.d.ts +10 -0
  97. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  98. package/dist/modes/interactive/interactive-mode.js +261 -37
  99. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  100. package/dist/modes/print-mode.js +1 -1
  101. package/dist/modes/print-mode.js.map +1 -1
  102. package/dist/modes/rpc/rpc-client.d.ts +6 -6
  103. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  104. package/dist/modes/rpc/rpc-client.js +3 -3
  105. package/dist/modes/rpc/rpc-client.js.map +1 -1
  106. package/dist/package-manager-cli.d.ts +2 -2
  107. package/dist/package-manager-cli.d.ts.map +1 -1
  108. package/dist/package-manager-cli.js +64 -17
  109. package/dist/package-manager-cli.js.map +1 -1
  110. package/dist/utils/clipboard-image.js +1 -1
  111. package/dist/utils/clipboard-image.js.map +1 -1
  112. package/docs/extensions.md +33 -9
  113. package/docs/models.md +4 -4
  114. package/docs/packages.md +7 -10
  115. package/docs/rpc.md +13 -3
  116. package/docs/sdk.md +22 -0
  117. package/docs/settings.md +1 -0
  118. package/docs/usage.md +1 -1
  119. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  120. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  121. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  122. package/examples/extensions/gondolin/package-lock.json +2 -2
  123. package/examples/extensions/gondolin/package.json +1 -1
  124. package/examples/extensions/reload-runtime.ts +2 -2
  125. package/examples/extensions/sandbox/package-lock.json +2 -2
  126. package/examples/extensions/sandbox/package.json +1 -1
  127. package/examples/extensions/with-deps/package-lock.json +2 -2
  128. package/examples/extensions/with-deps/package.json +1 -1
  129. package/examples/sdk/README.md +1 -1
  130. package/node_modules/@code-yeongyu/senpi-codemode/package.json +3 -3
  131. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  132. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +37 -2
  133. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  134. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-repo.d.ts.map +1 -1
  135. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-repo.js +2 -0
  136. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-repo.js.map +1 -1
  137. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.d.ts +1 -0
  138. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.d.ts.map +1 -1
  139. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.js +7 -0
  140. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.js.map +1 -1
  141. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.d.ts +18 -4
  142. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.d.ts.map +1 -1
  143. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +72 -37
  144. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js.map +1 -1
  145. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +2 -0
  146. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  147. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
  148. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts +2 -0
  149. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts.map +1 -1
  150. package/node_modules/@earendil-works/pi-agent-core/dist/index.js +2 -0
  151. package/node_modules/@earendil-works/pi-agent-core/dist/index.js.map +1 -1
  152. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  153. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
  154. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +2 -1
  155. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
  156. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +1 -1
  157. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js.map +1 -1
  158. package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.d.ts.map +1 -1
  159. package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.js +4 -1
  160. package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.js.map +1 -1
  161. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +446 -195
  162. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  163. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.d.ts +84 -0
  164. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.d.ts.map +1 -1
  165. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.js +72 -0
  166. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.models.js.map +1 -1
  167. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js +4 -4
  168. package/node_modules/@earendil-works/pi-ai/dist/providers/github-copilot.models.js.map +1 -1
  169. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.d.ts +63 -0
  170. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.d.ts.map +1 -1
  171. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.js +54 -0
  172. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-codex.models.js.map +1 -1
  173. package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.d.ts +84 -0
  174. package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.d.ts.map +1 -1
  175. package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.js +72 -0
  176. package/node_modules/@earendil-works/pi-ai/dist/providers/openai.models.js.map +1 -1
  177. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts +138 -63
  178. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.d.ts.map +1 -1
  179. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +136 -76
  180. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js.map +1 -1
  181. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts +77 -0
  182. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.d.ts.map +1 -1
  183. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +72 -1
  184. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js.map +1 -1
  185. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.d.ts +0 -44
  186. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.d.ts.map +1 -1
  187. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js +9 -45
  188. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-ams.models.js.map +1 -1
  189. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.d.ts +0 -44
  190. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.d.ts.map +1 -1
  191. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js +9 -45
  192. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-cn.models.js.map +1 -1
  193. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.d.ts +0 -44
  194. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.d.ts.map +1 -1
  195. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js +9 -45
  196. package/node_modules/@earendil-works/pi-ai/dist/providers/xiaomi-token-plan-sgp.models.js.map +1 -1
  197. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  198. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +3 -0
  199. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  200. package/node_modules/@earendil-works/pi-ai/package.json +1 -1
  201. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  202. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  203. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +22 -1
  204. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  205. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  206. package/package.json +7 -8
  207. package/npm-shrinkwrap.json +0 -3479
@@ -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,17 +123,27 @@ function buildGroups(resolved, agentDir) {
121
123
  return groups;
122
124
  }
123
125
  class ConfigSelectorHeader {
126
+ constructor(writeScope, projectModeAvailable) {
127
+ this.writeScope = writeScope;
128
+ this.projectModeAvailable = projectModeAvailable;
129
+ }
130
+ setWriteScope(writeScope) {
131
+ this.writeScope = writeScope;
132
+ }
124
133
  invalidate() { }
125
134
  render(width) {
126
- const title = theme.bold("Resource Configuration");
135
+ const title = theme.bold(this.writeScope === "project" ? "Project Local Resources" : "Global Resources");
127
136
  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);
137
+ const switchHint = this.projectModeAvailable ? keyHint("tui.input.tab", "switch mode") + sep : "";
138
+ const actionHint = this.writeScope === "project" ? rawKeyHint("space", "cycle inherit/+/-") : rawKeyHint("space", "toggle");
139
+ const hint = switchHint + actionHint + sep + rawKeyHint("esc", "close");
140
+ const spacing = Math.max(1, width - visibleWidth(title) - visibleWidth(hint));
141
+ const scopeHint = this.writeScope === "project"
142
+ ? theme.fg("muted", `${CONFIG_DIR_NAME}/settings.json · inherited global resources are dimmed`)
143
+ : theme.fg("muted", `~/${CONFIG_DIR_NAME}/agent/settings.json`);
132
144
  return [
133
145
  truncateToWidth(`${title}${" ".repeat(spacing)}${hint}`, width, ""),
134
- theme.fg("muted", "Type to filter resources"),
146
+ truncateToWidth(scopeHint, width, ""),
135
147
  ];
136
148
  }
137
149
  }
@@ -143,15 +155,17 @@ class ResourceList {
143
155
  this._focused = value;
144
156
  this.searchInput.focused = value;
145
157
  }
146
- constructor(groups, settingsManager, cwd, agentDir, terminalHeight) {
158
+ constructor(groupsByScope, settingsManager, cwd, agentDir, terminalHeight, writeScope = "global") {
147
159
  this.flatItems = [];
148
160
  this.filteredItems = [];
149
161
  this.selectedIndex = 0;
150
162
  this._focused = false;
151
- this.groups = groups;
163
+ this.groupsByScope = groupsByScope;
152
164
  this.settingsManager = settingsManager;
153
165
  this.cwd = cwd;
154
166
  this.agentDir = agentDir;
167
+ this.writeScope = writeScope;
168
+ this.inheritedEnabledByKey = this.buildInheritedEnabledMap(groupsByScope.global);
155
169
  this.searchInput = new Input();
156
170
  // 8 lines of chrome: top spacer + top border + spacer + header (2 lines) + spacer + bottom spacer + bottom border
157
171
  const chrome = 8;
@@ -159,6 +173,25 @@ class ResourceList {
159
173
  this.buildFlatList();
160
174
  this.filteredItems = [...this.flatItems];
161
175
  }
176
+ setWriteScope(writeScope) {
177
+ this.writeScope = writeScope;
178
+ this.buildFlatList();
179
+ this.filterItems(this.searchInput.getValue());
180
+ }
181
+ get groups() {
182
+ return this.groupsByScope[this.writeScope];
183
+ }
184
+ buildInheritedEnabledMap(groups) {
185
+ const result = new Map();
186
+ for (const group of groups) {
187
+ for (const subgroup of group.subgroups) {
188
+ for (const item of subgroup.items) {
189
+ result.set(this.getResourceItemKey(item), item.enabled);
190
+ }
191
+ }
192
+ }
193
+ return result;
194
+ }
162
195
  buildFlatList() {
163
196
  this.flatItems = [];
164
197
  for (const group of this.groups) {
@@ -265,21 +298,25 @@ class ResourceList {
265
298
  const isSelected = i === this.selectedIndex;
266
299
  if (entry.type === "group") {
267
300
  // Main group header (no cursor)
268
- const groupLine = theme.fg("accent", theme.bold(entry.group.label));
301
+ const inherited = this.writeScope === "project" && entry.group.scope === "user";
302
+ const label = theme.bold(`${entry.group.label}${inherited ? " · inherited global" : ""}`);
303
+ const groupLine = theme.fg(inherited ? "dim" : "accent", label);
269
304
  lines.push(truncateToWidth(` ${groupLine}`, width, ""));
270
305
  }
271
306
  else if (entry.type === "subgroup") {
272
307
  // Subgroup header (indented, no cursor)
273
- const subgroupLine = theme.fg("muted", entry.subgroup.label);
308
+ const color = this.writeScope === "project" && entry.group.scope === "user" ? "dim" : "muted";
309
+ const subgroupLine = theme.fg(color, entry.subgroup.label);
274
310
  lines.push(truncateToWidth(` ${subgroupLine}`, width, ""));
275
311
  }
276
312
  else {
277
313
  // Resource item (cursor only on items)
278
314
  const item = entry.item;
279
315
  const cursor = isSelected ? "> " : " ";
280
- const checkbox = item.enabled ? theme.fg("success", "[x]") : theme.fg("dim", "[ ]");
281
- const name = isSelected ? theme.bold(item.displayName) : item.displayName;
282
- lines.push(truncateToWidth(`${cursor} ${checkbox} ${name}`, width, "..."));
316
+ const dimmed = this.isDimmedItem(item);
317
+ const nameText = isSelected && !dimmed ? theme.bold(item.displayName) : item.displayName;
318
+ const name = dimmed ? theme.fg("dim", nameText) : nameText;
319
+ lines.push(truncateToWidth(`${cursor} ${this.renderCheckbox(item)} ${name}${this.getItemSuffix(item)}`, width, "..."));
283
320
  }
284
321
  }
285
322
  // Scroll indicator
@@ -330,13 +367,18 @@ class ResourceList {
330
367
  this.onExit?.();
331
368
  return;
332
369
  }
370
+ if (kb.matches(data, "tui.input.tab")) {
371
+ this.onSwitchMode?.();
372
+ return;
373
+ }
333
374
  if (data === " " || kb.matches(data, "tui.select.confirm")) {
334
375
  const entry = this.filteredItems[this.selectedIndex];
335
- if (entry?.type === "item") {
336
- const newEnabled = !entry.item.enabled;
337
- this.toggleResource(entry.item, newEnabled);
338
- this.updateItem(entry.item, newEnabled);
339
- this.onToggle?.(entry.item, newEnabled);
376
+ if (entry?.type === "item" && (this.writeScope === "project" || this.getItemScope(entry.item) === "user")) {
377
+ const newEnabled = this.toggleResource(entry.item);
378
+ if (newEnabled !== undefined) {
379
+ this.updateItem(entry.item, newEnabled);
380
+ this.onToggle?.(entry.item, newEnabled);
381
+ }
340
382
  }
341
383
  return;
342
384
  }
@@ -344,13 +386,21 @@ class ResourceList {
344
386
  this.searchInput.handleInput(data);
345
387
  this.filterItems(this.searchInput.getValue());
346
388
  }
347
- toggleResource(item, enabled) {
389
+ toggleResource(item) {
390
+ if (this.writeScope === "project") {
391
+ const state = this.getNextOverrideState(item);
392
+ if (!this.setProjectResourceOverride(item, state))
393
+ return undefined;
394
+ return state === "inherit" ? this.getInheritedEnabled(item) : state === "load";
395
+ }
396
+ const enabled = !item.enabled;
348
397
  if (item.metadata.origin === "top-level") {
349
398
  this.toggleTopLevelResource(item, enabled);
350
399
  }
351
400
  else {
352
401
  this.togglePackageResource(item, enabled);
353
402
  }
403
+ return enabled;
354
404
  }
355
405
  toggleTopLevelResource(item, enabled) {
356
406
  const scope = item.metadata.scope;
@@ -448,6 +498,188 @@ class ResourceList {
448
498
  this.settingsManager.setPackages(packages);
449
499
  }
450
500
  }
501
+ renderCheckbox(item) {
502
+ if (this.writeScope === "project") {
503
+ const state = this.getProjectOverrideState(item);
504
+ if (state === "load")
505
+ return theme.fg("success", "[+]");
506
+ if (state === "unload")
507
+ return theme.fg("warning", "[-]");
508
+ return theme.fg("dim", item.enabled ? "[x]" : "[ ]");
509
+ }
510
+ return item.enabled ? theme.fg("success", "[x]") : theme.fg("dim", "[ ]");
511
+ }
512
+ getItemSuffix(item) {
513
+ if (this.writeScope !== "project")
514
+ return "";
515
+ const state = this.getProjectOverrideState(item);
516
+ if (state === "load")
517
+ return theme.fg("muted", " project load");
518
+ if (state === "unload")
519
+ return theme.fg("muted", " project unload");
520
+ return this.isInheritedGlobalItem(item) ? theme.fg("dim", " inherited global") : "";
521
+ }
522
+ isDimmedItem(item) {
523
+ return (this.writeScope === "project" &&
524
+ this.isInheritedGlobalItem(item) &&
525
+ this.getProjectOverrideState(item) === "inherit");
526
+ }
527
+ setProjectResourceOverride(item, state) {
528
+ return item.metadata.origin === "top-level"
529
+ ? this.setProjectTopLevelOverride(item, state)
530
+ : this.setProjectPackageOverride(item, state);
531
+ }
532
+ setProjectTopLevelOverride(item, state) {
533
+ const current = (this.settingsManager.getProjectSettings()[item.resourceType] ?? []);
534
+ const pattern = this.isInheritedGlobalItem(item) ? item.path : this.getResourcePatternForScope(item, "project");
535
+ const patterns = this.getTopLevelOverridePatterns(item, "project");
536
+ const updated = current.filter((entry) => {
537
+ const target = this.getPatternEntryTarget(entry);
538
+ if ((entry.startsWith("!") || entry.startsWith("+") || entry.startsWith("-")) && patterns.has(target))
539
+ return false;
540
+ return !(state === "inherit" && this.isInheritedGlobalItem(item) && target === pattern);
541
+ });
542
+ if (state !== "inherit") {
543
+ if (this.isInheritedGlobalItem(item) && !updated.includes(pattern))
544
+ updated.push(pattern);
545
+ updated.push(`${state === "load" ? "+" : "-"}${pattern}`);
546
+ }
547
+ this.setProjectTopLevelPaths(item.resourceType, updated);
548
+ return true;
549
+ }
550
+ setProjectTopLevelPaths(key, paths) {
551
+ if (key === "extensions")
552
+ this.settingsManager.setProjectExtensionPaths(paths);
553
+ else if (key === "skills")
554
+ this.settingsManager.setProjectSkillPaths(paths);
555
+ else if (key === "prompts")
556
+ this.settingsManager.setProjectPromptTemplatePaths(paths);
557
+ else
558
+ this.settingsManager.setProjectThemePaths(paths);
559
+ }
560
+ setProjectPackageOverride(item, state) {
561
+ const packages = [...(this.settingsManager.getProjectSettings().packages ?? [])];
562
+ let pkgIndex = packages.findIndex((pkg) => this.packageSourceStringMatches(item.metadata.source, this.getItemScope(item), typeof pkg === "string" ? pkg : pkg.source, "project"));
563
+ if (pkgIndex === -1) {
564
+ if (state === "inherit")
565
+ return false;
566
+ packages.push(this.createPackageOverrideSource(item));
567
+ pkgIndex = packages.length - 1;
568
+ }
569
+ let pkg = packages[pkgIndex];
570
+ if (pkg === undefined)
571
+ return false;
572
+ if (typeof pkg === "string") {
573
+ pkg = { source: pkg };
574
+ packages[pkgIndex] = pkg;
575
+ }
576
+ const pattern = this.getPackageResourcePattern(item);
577
+ const updated = (pkg[item.resourceType] ?? []).filter((entry) => this.getPatternEntryTarget(entry) !== pattern);
578
+ if (state !== "inherit")
579
+ updated.push(`${state === "load" ? "+" : "-"}${pattern}`);
580
+ pkg[item.resourceType] = updated.length > 0 ? updated : undefined;
581
+ if (!RESOURCE_TYPES.some((key) => pkg[key] !== undefined)) {
582
+ if (pkg.autoload === false)
583
+ packages.splice(pkgIndex, 1);
584
+ else
585
+ packages[pkgIndex] = pkg.source;
586
+ }
587
+ this.settingsManager.setProjectPackages(packages);
588
+ return true;
589
+ }
590
+ getNextOverrideState(item) {
591
+ const state = this.getProjectOverrideState(item);
592
+ const inheritedEnabled = this.getInheritedEnabled(item);
593
+ if (state === "inherit")
594
+ return inheritedEnabled ? "unload" : "load";
595
+ if (state === "unload")
596
+ return inheritedEnabled ? "load" : "inherit";
597
+ return inheritedEnabled ? "inherit" : "unload";
598
+ }
599
+ getProjectOverrideState(item) {
600
+ if (this.writeScope !== "project")
601
+ return "inherit";
602
+ if (item.metadata.origin === "top-level") {
603
+ return this.getOverrideStateFromEntries((this.settingsManager.getProjectSettings()[item.resourceType] ?? []), this.getTopLevelOverridePatterns(item, "project"), false);
604
+ }
605
+ const pkg = this.findMatchingPackageSource(item, "project");
606
+ if (typeof pkg !== "object")
607
+ return "inherit";
608
+ const entries = pkg[item.resourceType];
609
+ if (entries === undefined)
610
+ return "inherit";
611
+ return this.getOverrideStateFromEntries(entries, new Set([this.getPackageResourcePattern(item)]), pkg.autoload !== false);
612
+ }
613
+ getOverrideStateFromEntries(entries, patterns, emptyArrayIsUnload) {
614
+ if (entries.length === 0 && emptyArrayIsUnload)
615
+ return "unload";
616
+ let state = "inherit";
617
+ for (const entry of entries) {
618
+ if (!patterns.has(this.getPatternEntryTarget(entry)))
619
+ continue;
620
+ if (entry.startsWith("!") || entry.startsWith("-"))
621
+ state = "unload";
622
+ else
623
+ state = "load";
624
+ }
625
+ return state;
626
+ }
627
+ getInheritedEnabled(item) {
628
+ return (this.inheritedEnabledByKey.get(this.getResourceItemKey(item)) ??
629
+ (this.getItemScope(item) === "user" ? item.enabled : true));
630
+ }
631
+ isInheritedGlobalItem(item) {
632
+ return this.getItemScope(item) === "user" || this.inheritedEnabledByKey.has(this.getResourceItemKey(item));
633
+ }
634
+ getTopLevelOverridePatterns(item, scope) {
635
+ const baseDir = this.getTopLevelBaseDir(scope);
636
+ const patterns = new Set([
637
+ this.getResourcePatternForScope(item, scope),
638
+ item.path,
639
+ relative(baseDir, item.path),
640
+ ]);
641
+ if (item.metadata.baseDir)
642
+ patterns.add(relative(item.metadata.baseDir, item.path));
643
+ return patterns;
644
+ }
645
+ getResourcePatternForScope(item, scope) {
646
+ const sourceScope = this.getItemScope(item);
647
+ if (scope !== sourceScope)
648
+ return item.path;
649
+ const baseDir = item.metadata.baseDir ?? this.getTopLevelBaseDir(sourceScope);
650
+ return relative(baseDir, item.path);
651
+ }
652
+ createPackageOverrideSource(item) {
653
+ const source = item.metadata.source;
654
+ if (!isLocalPath(source))
655
+ return { source, autoload: false };
656
+ const sourcePath = resolvePath(source, this.getTopLevelBaseDir(this.getItemScope(item)), { trim: true });
657
+ return { source: relative(this.getTopLevelBaseDir("project"), sourcePath) || ".", autoload: false };
658
+ }
659
+ packageSourceStringMatches(leftSource, leftScope, rightSource, rightScope) {
660
+ if (leftSource === rightSource)
661
+ return true;
662
+ if (!isLocalPath(leftSource) || !isLocalPath(rightSource))
663
+ return false;
664
+ const left = resolvePath(leftSource, this.getTopLevelBaseDir(leftScope), { trim: true });
665
+ const right = resolvePath(rightSource, this.getTopLevelBaseDir(rightScope), { trim: true });
666
+ return left === right;
667
+ }
668
+ findMatchingPackageSource(item, targetScope) {
669
+ const settings = targetScope === "project"
670
+ ? this.settingsManager.getProjectSettings()
671
+ : this.settingsManager.getGlobalSettings();
672
+ return (settings.packages ?? []).find((pkg) => this.packageSourceStringMatches(item.metadata.source, this.getItemScope(item), typeof pkg === "string" ? pkg : pkg.source, targetScope));
673
+ }
674
+ getPatternEntryTarget(entry) {
675
+ return entry.startsWith("!") || entry.startsWith("+") || entry.startsWith("-") ? entry.slice(1) : entry;
676
+ }
677
+ getResourceItemKey(item) {
678
+ return `${item.resourceType}:${canonicalizePath(item.path)}`;
679
+ }
680
+ getItemScope(item) {
681
+ return item.metadata.scope === "project" ? "project" : "user";
682
+ }
451
683
  getTopLevelBaseDir(scope) {
452
684
  return scope === "project" ? join(this.cwd, CONFIG_DIR_NAME) : this.agentDir;
453
685
  }
@@ -469,26 +701,42 @@ export class ConfigSelectorComponent extends Container {
469
701
  this._focused = value;
470
702
  this.resourceList.focused = value;
471
703
  }
472
- constructor(resolvedPaths, settingsManager, cwd, agentDir, onClose, onExit, requestRender, terminalHeight) {
704
+ constructor(resolvedPaths, settingsManager, cwd, agentDir, onClose, onExit, requestRender, terminalHeight, writeScope = "global", projectModeAvailable = true) {
473
705
  super();
474
706
  this._focused = false;
475
- const groups = buildGroups(resolvedPaths, agentDir);
707
+ this.writeScope = writeScope;
708
+ const groupsByScope = {
709
+ global: buildGroups(resolvedPaths.global, agentDir),
710
+ project: buildGroups(resolvedPaths.project, agentDir),
711
+ };
476
712
  // Add header
477
713
  this.addChild(new Spacer(1));
478
714
  this.addChild(new DynamicBorder());
479
715
  this.addChild(new Spacer(1));
480
- this.addChild(new ConfigSelectorHeader());
716
+ this.header = new ConfigSelectorHeader(this.writeScope, projectModeAvailable);
717
+ this.addChild(this.header);
481
718
  this.addChild(new Spacer(1));
482
719
  // Resource list
483
- this.resourceList = new ResourceList(groups, settingsManager, cwd, agentDir, terminalHeight);
720
+ this.resourceList = new ResourceList(groupsByScope, settingsManager, cwd, agentDir, terminalHeight, this.writeScope);
484
721
  this.resourceList.onCancel = onClose;
485
722
  this.resourceList.onExit = onExit;
486
723
  this.resourceList.onToggle = () => requestRender();
724
+ if (projectModeAvailable) {
725
+ this.resourceList.onSwitchMode = () => {
726
+ this.switchWriteScope();
727
+ requestRender();
728
+ };
729
+ }
487
730
  this.addChild(this.resourceList);
488
731
  // Bottom border
489
732
  this.addChild(new Spacer(1));
490
733
  this.addChild(new DynamicBorder());
491
734
  }
735
+ switchWriteScope() {
736
+ this.writeScope = this.writeScope === "global" ? "project" : "global";
737
+ this.header.setWriteScope(this.writeScope);
738
+ this.resourceList.setWriteScope(this.writeScope);
739
+ }
492
740
  getResourceList() {
493
741
  return this.resourceList;
494
742
  }