@pellux/goodvibes-agent 0.1.116 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/CHANGELOG.md +48 -661
  2. package/README.md +64 -57
  3. package/bin/goodvibes-agent.ts +16 -2
  4. package/dist/package/main.js +199180 -192185
  5. package/docs/README.md +15 -8
  6. package/docs/channels-remote-and-api.md +50 -0
  7. package/docs/connected-host.md +59 -0
  8. package/docs/getting-started.md +61 -50
  9. package/docs/knowledge-artifacts-and-multimodal.md +91 -0
  10. package/docs/project-planning.md +79 -0
  11. package/docs/providers-and-routing.md +46 -0
  12. package/docs/release-and-publishing.md +48 -11
  13. package/docs/tools-and-commands.md +123 -0
  14. package/docs/voice-and-live-tts.md +51 -0
  15. package/package.json +3 -6
  16. package/src/agent/behavior-discovery-summary.ts +1 -3
  17. package/src/agent/channel-delivery.ts +201 -0
  18. package/src/agent/media-generation.ts +159 -0
  19. package/src/agent/memory-prompt.ts +0 -1
  20. package/src/agent/note-registry.ts +329 -0
  21. package/src/agent/operator-actions.ts +343 -0
  22. package/src/agent/persona-discovery.ts +1 -3
  23. package/src/agent/persona-registry.ts +19 -16
  24. package/src/agent/record-labels.ts +107 -0
  25. package/src/agent/reminder-schedule-format.ts +33 -24
  26. package/src/agent/reminder-schedule.ts +27 -26
  27. package/src/agent/routine-registry.ts +54 -14
  28. package/src/agent/routine-schedule-args.ts +2 -1
  29. package/src/agent/routine-schedule-format.ts +77 -53
  30. package/src/agent/routine-schedule-promotion.ts +35 -33
  31. package/src/agent/routine-schedule-receipts.ts +29 -27
  32. package/src/agent/runtime-profile-starters.ts +5 -5
  33. package/src/agent/runtime-profile.ts +91 -12
  34. package/src/agent/skill-registry.ts +210 -32
  35. package/src/cli/agent-knowledge-args.ts +5 -1
  36. package/src/cli/agent-knowledge-command.ts +40 -34
  37. package/src/cli/agent-knowledge-format.ts +80 -67
  38. package/src/cli/agent-knowledge-methods.ts +1 -1
  39. package/src/cli/agent-knowledge-runtime.ts +32 -26
  40. package/src/cli/bundle-command.ts +14 -9
  41. package/src/cli/config-overrides.ts +37 -36
  42. package/src/cli/entrypoint.ts +16 -7
  43. package/src/cli/external-runtime.ts +10 -4
  44. package/src/cli/help.ts +54 -29
  45. package/src/cli/local-library-command.ts +203 -83
  46. package/src/cli/management-commands.ts +105 -68
  47. package/src/cli/management.ts +55 -74
  48. package/src/cli/memory-command.ts +66 -32
  49. package/src/cli/parser.ts +37 -3
  50. package/src/cli/profiles-command.ts +166 -31
  51. package/src/cli/provider-auth-routes.ts +2 -1
  52. package/src/cli/routines-command.ts +176 -40
  53. package/src/cli/service-posture.ts +16 -16
  54. package/src/cli/status.ts +56 -135
  55. package/src/core/conversation-message-snapshot.ts +131 -0
  56. package/src/input/agent-workspace-access-command-editor-submission.ts +149 -0
  57. package/src/input/agent-workspace-access-command-editors.ts +170 -0
  58. package/src/input/agent-workspace-activation.ts +35 -8
  59. package/src/input/agent-workspace-basic-command-editor-submission.ts +351 -94
  60. package/src/input/agent-workspace-basic-command-editors.ts +502 -9
  61. package/src/input/agent-workspace-categories.ts +423 -51
  62. package/src/input/agent-workspace-channel-command-editor-submission.ts +83 -0
  63. package/src/input/agent-workspace-channel-command-editors.ts +65 -0
  64. package/src/input/agent-workspace-channels.ts +35 -2
  65. package/src/input/agent-workspace-command-editor.ts +41 -3
  66. package/src/input/agent-workspace-config-reader.ts +16 -0
  67. package/src/input/agent-workspace-delegation-editor-submission.ts +55 -0
  68. package/src/input/agent-workspace-editors.ts +166 -1
  69. package/src/input/agent-workspace-knowledge-command-editor-submission.ts +132 -0
  70. package/src/input/agent-workspace-knowledge-command-editors.ts +110 -0
  71. package/src/input/agent-workspace-knowledge-query-editor.ts +1 -1
  72. package/src/input/agent-workspace-learned-behavior.ts +67 -0
  73. package/src/input/agent-workspace-library-command-editor-submission.ts +50 -0
  74. package/src/input/agent-workspace-library-command-editors.ts +35 -0
  75. package/src/input/agent-workspace-local-operations.ts +218 -0
  76. package/src/input/agent-workspace-local-selection.ts +75 -0
  77. package/src/input/agent-workspace-mcp-command-editor-submission.ts +95 -0
  78. package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
  79. package/src/input/agent-workspace-media-command-editors.ts +27 -0
  80. package/src/input/agent-workspace-memory-command-editor-submission.ts +232 -0
  81. package/src/input/agent-workspace-memory-command-editors.ts +180 -0
  82. package/src/input/agent-workspace-memory-editor.ts +2 -2
  83. package/src/input/agent-workspace-navigation.ts +81 -0
  84. package/src/input/agent-workspace-notify-editor-submission.ts +121 -0
  85. package/src/input/agent-workspace-operations-command-editor-submission.ts +208 -0
  86. package/src/input/agent-workspace-operations-command-editors.ts +196 -0
  87. package/src/input/agent-workspace-panel-route.ts +43 -0
  88. package/src/input/agent-workspace-provider-command-editor-submission.ts +155 -0
  89. package/src/input/agent-workspace-provider-command-editors.ts +93 -0
  90. package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
  91. package/src/input/agent-workspace-requirements.ts +11 -0
  92. package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
  93. package/src/input/agent-workspace-search.ts +169 -0
  94. package/src/input/agent-workspace-secret-editor-submission.ts +153 -0
  95. package/src/input/agent-workspace-session-command-editor-submission.ts +199 -0
  96. package/src/input/agent-workspace-session-command-editors.ts +249 -0
  97. package/src/input/agent-workspace-setup.ts +37 -16
  98. package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +101 -0
  99. package/src/input/agent-workspace-skill-bundle-command-editors.ts +110 -0
  100. package/src/input/agent-workspace-snapshot.ts +162 -21
  101. package/src/input/agent-workspace-task-command-editor-submission.ts +61 -0
  102. package/src/input/agent-workspace-task-command-editors.ts +47 -0
  103. package/src/input/agent-workspace-token.ts +18 -2
  104. package/src/input/agent-workspace-types.ts +212 -4
  105. package/src/input/agent-workspace-voice-media.ts +4 -7
  106. package/src/input/agent-workspace-web-research-editor.ts +104 -0
  107. package/src/input/agent-workspace-workplan-editor-submission.ts +153 -0
  108. package/src/input/agent-workspace.ts +178 -240
  109. package/src/input/command-registry.ts +5 -2
  110. package/src/input/commands/agent-runtime-profile-runtime.ts +126 -37
  111. package/src/input/commands/agent-skills-runtime.ts +149 -72
  112. package/src/input/commands/agent-workspace-runtime.ts +23 -6
  113. package/src/input/commands/brief-runtime.ts +55 -25
  114. package/src/input/commands/channels-runtime.ts +147 -33
  115. package/src/input/commands/compat-runtime.ts +32 -0
  116. package/src/input/commands/delegation-runtime.ts +29 -23
  117. package/src/input/commands/experience-runtime.ts +6 -4
  118. package/src/input/commands/guidance-runtime.ts +4 -4
  119. package/src/input/commands/health-runtime.ts +149 -126
  120. package/src/input/commands/knowledge.ts +82 -60
  121. package/src/input/commands/local-provider-runtime.ts +43 -21
  122. package/src/input/commands/local-runtime.ts +138 -16
  123. package/src/input/commands/local-setup-review.ts +10 -20
  124. package/src/input/commands/mcp-runtime.ts +63 -46
  125. package/src/input/commands/notify-runtime.ts +38 -9
  126. package/src/input/commands/operator-actions-runtime.ts +138 -0
  127. package/src/input/commands/operator-runtime.ts +6 -76
  128. package/src/input/commands/personas-runtime.ts +46 -31
  129. package/src/input/commands/planning-runtime.ts +1 -1
  130. package/src/input/commands/platform-access-runtime.ts +35 -98
  131. package/src/input/commands/product-runtime.ts +2 -2
  132. package/src/input/commands/provider-accounts-runtime.ts +60 -39
  133. package/src/input/commands/qrcode-runtime.ts +50 -9
  134. package/src/input/commands/recall-bundle.ts +11 -11
  135. package/src/input/commands/recall-capture.ts +13 -11
  136. package/src/input/commands/recall-query.ts +29 -21
  137. package/src/input/commands/recall-review.ts +2 -1
  138. package/src/input/commands/routines-runtime.ts +99 -45
  139. package/src/input/commands/runtime-services.ts +1 -14
  140. package/src/input/commands/schedule-runtime.ts +33 -20
  141. package/src/input/commands/security-runtime.ts +6 -11
  142. package/src/input/commands/session-content.ts +80 -78
  143. package/src/input/commands/session-workflow.ts +134 -104
  144. package/src/input/commands/session.ts +3 -174
  145. package/src/input/commands/shell-core.ts +38 -17
  146. package/src/input/commands/subscription-runtime.ts +54 -185
  147. package/src/input/commands/support-bundle-runtime.ts +53 -0
  148. package/src/input/commands/tasks-runtime.ts +21 -21
  149. package/src/input/commands/work-plan-runtime.ts +33 -8
  150. package/src/input/commands.ts +6 -2
  151. package/src/input/feed-context-factory.ts +2 -2
  152. package/src/input/file-picker.ts +3 -2
  153. package/src/input/handler-command-route.ts +4 -7
  154. package/src/input/handler-content-actions.ts +89 -26
  155. package/src/input/handler-feed-routes.ts +19 -12
  156. package/src/input/handler-feed.ts +6 -5
  157. package/src/input/handler-interactions.ts +9 -36
  158. package/src/input/handler-modal-stack.ts +3 -3
  159. package/src/input/handler-onboarding.ts +52 -91
  160. package/src/input/handler-shortcuts.ts +16 -7
  161. package/src/input/handler.ts +50 -23
  162. package/src/input/input-history.ts +5 -6
  163. package/src/input/keybindings.ts +27 -16
  164. package/src/input/mcp-workspace.ts +90 -26
  165. package/src/input/onboarding/handler-onboarding-routes.ts +3 -1
  166. package/src/input/onboarding/onboarding-wizard-apply.ts +62 -0
  167. package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
  168. package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
  169. package/src/input/onboarding/onboarding-wizard-operator-steps.ts +223 -55
  170. package/src/input/onboarding/onboarding-wizard-steps.ts +59 -35
  171. package/src/input/onboarding/onboarding-wizard-types.ts +4 -1
  172. package/src/input/onboarding/onboarding-wizard.ts +87 -2
  173. package/src/input/profile-picker-modal.ts +31 -12
  174. package/src/input/session-picker-modal.ts +21 -4
  175. package/src/input/settings-modal-agent-policy.ts +6 -6
  176. package/src/input/settings-modal-behavior.ts +0 -3
  177. package/src/input/settings-modal-subscriptions.ts +3 -3
  178. package/src/input/settings-modal-types.ts +2 -13
  179. package/src/input/settings-modal.ts +20 -66
  180. package/src/input/submission-intent.ts +0 -1
  181. package/src/input/submission-router.ts +3 -3
  182. package/src/main.ts +18 -10
  183. package/src/panels/approval-panel.ts +8 -8
  184. package/src/panels/automation-control-panel.ts +4 -4
  185. package/src/panels/base-panel.ts +1 -1
  186. package/src/panels/builtin/agent.ts +1 -1
  187. package/src/panels/builtin/operations.ts +3 -12
  188. package/src/panels/builtin/session.ts +32 -24
  189. package/src/panels/builtin/shared.ts +7 -7
  190. package/src/panels/cost-tracker-panel.ts +1 -1
  191. package/src/panels/docs-panel.ts +5 -3
  192. package/src/panels/index.ts +0 -1
  193. package/src/panels/knowledge-panel.ts +6 -5
  194. package/src/panels/memory-panel.ts +7 -6
  195. package/src/panels/panel-list-panel.ts +36 -80
  196. package/src/panels/project-planning-panel.ts +19 -12
  197. package/src/panels/provider-account-snapshot.ts +51 -25
  198. package/src/panels/provider-accounts-panel.ts +33 -18
  199. package/src/panels/provider-health-domains.ts +48 -7
  200. package/src/panels/provider-health-panel.ts +5 -4
  201. package/src/panels/qr-panel.ts +44 -20
  202. package/src/panels/schedule-panel.ts +9 -17
  203. package/src/panels/security-panel.ts +8 -8
  204. package/src/panels/session-browser-panel.ts +10 -10
  205. package/src/panels/subscription-panel.ts +6 -6
  206. package/src/panels/system-messages-panel.ts +3 -3
  207. package/src/panels/tasks-panel.ts +21 -14
  208. package/src/panels/tool-inspector-panel.ts +19 -12
  209. package/src/panels/work-plan-panel.ts +5 -5
  210. package/src/planning/project-planning-coordinator.ts +3 -3
  211. package/src/provider-auth-route-display.ts +9 -0
  212. package/src/renderer/agent-workspace-style.ts +34 -0
  213. package/src/renderer/agent-workspace.ts +254 -57
  214. package/src/renderer/autocomplete-overlay.ts +25 -6
  215. package/src/renderer/block-actions.ts +1 -3
  216. package/src/renderer/bookmark-modal.ts +19 -4
  217. package/src/renderer/buffer.ts +4 -2
  218. package/src/renderer/context-inspector.ts +50 -13
  219. package/src/renderer/diff.ts +1 -1
  220. package/src/renderer/file-picker-overlay.ts +19 -6
  221. package/src/renderer/help-overlay.ts +112 -34
  222. package/src/renderer/history-search-overlay.ts +19 -4
  223. package/src/renderer/live-tail-modal.ts +27 -5
  224. package/src/renderer/mcp-workspace.ts +176 -43
  225. package/src/renderer/model-picker-overlay.ts +58 -3
  226. package/src/renderer/model-workspace.ts +104 -22
  227. package/src/renderer/onboarding/onboarding-wizard.ts +20 -4
  228. package/src/renderer/process-modal.ts +27 -6
  229. package/src/renderer/profile-picker-modal.ts +21 -5
  230. package/src/renderer/search-overlay.ts +25 -5
  231. package/src/renderer/selection-modal-overlay.ts +46 -14
  232. package/src/renderer/session-picker-modal.ts +18 -1
  233. package/src/renderer/settings-modal-helpers.ts +2 -40
  234. package/src/renderer/settings-modal.ts +88 -55
  235. package/src/renderer/system-message.ts +1 -1
  236. package/src/renderer/tool-call.ts +20 -11
  237. package/src/runtime/agent-runtime-events.ts +129 -0
  238. package/src/runtime/bootstrap-command-context.ts +7 -1
  239. package/src/runtime/bootstrap-command-parts.ts +11 -8
  240. package/src/runtime/bootstrap-core.ts +23 -65
  241. package/src/runtime/bootstrap-hook-bridge.ts +7 -18
  242. package/src/runtime/bootstrap-shell.ts +46 -8
  243. package/src/runtime/bootstrap.ts +36 -46
  244. package/src/runtime/connected-host-auth.ts +47 -0
  245. package/src/runtime/diagnostics/panels/index.ts +2 -2
  246. package/src/runtime/diagnostics/panels/panel-resources.ts +1 -1
  247. package/src/runtime/diagnostics/panels/policy.ts +7 -7
  248. package/src/runtime/index.ts +2 -5
  249. package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
  250. package/src/runtime/onboarding/apply.ts +288 -74
  251. package/src/runtime/onboarding/derivation.ts +10 -13
  252. package/src/runtime/onboarding/snapshot.ts +2 -17
  253. package/src/runtime/onboarding/types.ts +34 -20
  254. package/src/runtime/onboarding/verify.ts +105 -4
  255. package/src/runtime/operator-token-cleanup.ts +3 -3
  256. package/src/runtime/services.ts +95 -11
  257. package/src/runtime/store/selectors/index.ts +3 -3
  258. package/src/runtime/store/state.ts +1 -4
  259. package/src/runtime/surface-feature-flags.ts +41 -6
  260. package/src/runtime/ui-read-models.ts +3 -4
  261. package/src/runtime/ui-services.ts +6 -6
  262. package/src/shell/blocking-input.ts +2 -1
  263. package/src/shell/service-settings-sync.ts +7 -7
  264. package/src/shell/ui-openers.ts +17 -38
  265. package/src/tools/agent-analysis-registry-policy.ts +0 -1
  266. package/src/tools/agent-channel-send-tool.ts +133 -0
  267. package/src/tools/agent-context-policy.ts +1 -1
  268. package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
  269. package/src/tools/agent-knowledge-tool.ts +170 -0
  270. package/src/tools/agent-local-registry-tool.ts +279 -73
  271. package/src/tools/agent-media-generate-tool.ts +133 -0
  272. package/src/tools/agent-notify-tool.ts +143 -0
  273. package/src/tools/agent-operator-action-tool.ts +137 -0
  274. package/src/tools/agent-operator-briefing-tool.ts +217 -0
  275. package/src/tools/agent-reminder-schedule-tool.ts +237 -0
  276. package/src/tools/agent-tool-policy-guard.ts +13 -15
  277. package/src/tools/agent-work-plan-tool.ts +256 -0
  278. package/src/version.ts +1 -1
  279. package/docs/connected-services.md +0 -51
  280. package/src/cli/package-verification.ts +0 -274
  281. package/src/input/commands/policy-dispatch.ts +0 -339
  282. package/src/input/commands/policy.ts +0 -17
  283. package/src/panels/panel-picker.ts +0 -105
  284. package/src/panels/policy-panel.ts +0 -308
  285. package/src/renderer/panel-picker-overlay.ts +0 -202
@@ -1,9 +1,14 @@
1
1
  import { createShellPathService } from '@/runtime/index.ts';
2
2
  import { discoverPersonas, type DiscoveredPersonaRecord } from '../agent/persona-discovery.ts';
3
3
  import { AgentPersonaRegistry, type AgentPersonaRecord } from '../agent/persona-registry.ts';
4
+ import { formatAgentRecordOrigin, formatAgentRecordReviewState } from '../agent/record-labels.ts';
4
5
  import { discoverSkills, type SkillRecord } from '../agent/skill-discovery.ts';
5
6
  import {
6
7
  AgentSkillRegistry,
8
+ buildAgentSkillRequirements,
9
+ evaluateAgentSkillBundleReadiness,
10
+ evaluateAgentSkillReadiness,
11
+ formatAgentSkillRequirement,
7
12
  type AgentSkillBundleRecord,
8
13
  type AgentSkillRecord,
9
14
  } from '../agent/skill-registry.ts';
@@ -118,8 +123,8 @@ function shellPaths(runtime: CliCommandRuntime): ReturnType<typeof createShellPa
118
123
 
119
124
  function summarizePersona(persona: AgentPersonaRecord, activePersonaId: string | null): string {
120
125
  const active = persona.id === activePersonaId ? 'active' : 'available';
121
- const tags = persona.tags.length > 0 ? ` tags=${persona.tags.join(',')}` : '';
122
- return ` ${persona.id} ${active} ${persona.reviewState} ${persona.name} - ${persona.description}${tags}`;
126
+ const tags = persona.tags.length > 0 ? ` tags ${persona.tags.join(', ')}` : '';
127
+ return ` ${persona.id} ${active} ${formatAgentRecordReviewState(persona.reviewState)} ${persona.name} - ${persona.description}${tags}`;
123
128
  }
124
129
 
125
130
  function renderPersonaList(title: string, path: string, personas: readonly AgentPersonaRecord[], activePersonaId: string | null): string {
@@ -127,12 +132,13 @@ function renderPersonaList(title: string, path: string, personas: readonly Agent
127
132
  return [
128
133
  title,
129
134
  ' No local Agent personas yet.',
135
+ ' No Agent-local personas yet.',
130
136
  ' Create one with: goodvibes-agent personas create --name <name> --description <summary> --body <instructions>',
131
137
  ].join('\n');
132
138
  }
133
139
  return [
134
140
  `${title} (${personas.length})`,
135
- ` store: ${path}`,
141
+ ` store ${path}`,
136
142
  ...personas.map((persona) => summarizePersona(persona, activePersonaId)),
137
143
  ].join('\n');
138
144
  }
@@ -140,11 +146,10 @@ function renderPersonaList(title: string, path: string, personas: readonly Agent
140
146
  function renderPersona(persona: AgentPersonaRecord, activePersonaId: string | null): string {
141
147
  return [
142
148
  `Persona ${persona.name}`,
143
- ` id: ${persona.id}`,
149
+ ` id ${persona.id}`,
144
150
  ` active: ${persona.id === activePersonaId ? 'yes' : 'no'}`,
145
- ` review: ${persona.reviewState}`,
146
- ` source: ${persona.source}`,
147
- ` provenance: ${persona.provenance}`,
151
+ ` review: ${formatAgentRecordReviewState(persona.reviewState)}`,
152
+ ` origin: ${formatAgentRecordOrigin(persona.source, persona.provenance)}`,
148
153
  ` tags: ${persona.tags.join(', ') || '(none)'}`,
149
154
  ` triggers: ${persona.triggers.join(', ') || '(manual)'}`,
150
155
  ` created: ${persona.createdAt}`,
@@ -161,7 +166,7 @@ function summarizeDiscoveredPersona(persona: DiscoveredPersonaRecord): string {
161
166
  const description = persona.description ? ` - ${persona.description}` : '';
162
167
  return [
163
168
  ` ${persona.name} ${persona.origin}${description}`,
164
- ` path: ${persona.path}`,
169
+ ` path ${persona.path}`,
165
170
  ].join('\n');
166
171
  }
167
172
 
@@ -170,7 +175,7 @@ function renderDiscoveredPersonaList(personas: readonly DiscoveredPersonaRecord[
170
175
  return [
171
176
  'Discovered Agent persona files',
172
177
  ' No persona markdown files found in Agent persona folders.',
173
- ' Search roots: .goodvibes/personas, .goodvibes/agent/personas, .goodvibes/agents, ~/.goodvibes/personas, ~/.goodvibes/agent/personas, ~/.goodvibes/agents',
178
+ ' Search roots: .goodvibes/personas, .goodvibes/agent/personas, ~/.goodvibes/personas, ~/.goodvibes/agent/personas',
174
179
  ].join('\n');
175
180
  }
176
181
  return [
@@ -203,37 +208,46 @@ function discoveredPersonaFrontmatterList(persona: DiscoveredPersonaRecord, key:
203
208
 
204
209
  function summarizeSkill(skill: AgentSkillRecord): string {
205
210
  const enabled = skill.enabled ? 'enabled' : 'disabled';
206
- const tags = skill.tags.length > 0 ? ` tags=${skill.tags.join(',')}` : '';
207
- return ` ${skill.id} ${enabled} ${skill.reviewState} ${skill.name} - ${skill.description}${tags}`;
211
+ const tags = skill.tags.length > 0 ? ` tags ${skill.tags.join(', ')}` : '';
212
+ const readiness = evaluateAgentSkillReadiness(skill);
213
+ const ready = readiness.ready ? 'ready' : `needs ${readiness.missing.map(formatAgentSkillRequirement).join(',')}`;
214
+ return ` ${skill.id} ${enabled} ${formatAgentRecordReviewState(skill.reviewState)} ${ready} ${skill.name} - ${skill.description}${tags}`;
208
215
  }
209
216
 
210
- function summarizeBundle(bundle: AgentSkillBundleRecord): string {
217
+ function summarizeBundle(bundle: AgentSkillBundleRecord, skills: readonly AgentSkillRecord[]): string {
211
218
  const enabled = bundle.enabled ? 'enabled' : 'disabled';
212
- return ` ${bundle.id} ${enabled} ${bundle.reviewState} ${bundle.name} - ${bundle.description} skills=${bundle.skillIds.join(',')}`;
219
+ const readiness = evaluateAgentSkillBundleReadiness(bundle, skills);
220
+ const missing = [
221
+ ...readiness.missingRequirements.map(formatAgentSkillRequirement),
222
+ ...readiness.missingSkillIds.map((skillId) => `skill:${skillId}`),
223
+ ];
224
+ const ready = readiness.ready ? 'ready' : `needs ${missing.join(',')}`;
225
+ return ` ${bundle.id} ${enabled} ${formatAgentRecordReviewState(bundle.reviewState)} ${ready} ${bundle.name} - ${bundle.description} skills ${bundle.skillIds.join(',')}`;
213
226
  }
214
227
 
215
- function renderSkillList(title: string, path: string, skills: readonly AgentSkillRecord[]): string {
228
+ function renderSkillList(title: string, path: string, skills: readonly AgentSkillRecord[], emptyMessage?: string): string {
216
229
  if (skills.length === 0) {
217
230
  return [
218
231
  title,
219
- ' No local Agent skills yet.',
220
- ' Create one with: goodvibes-agent skills create --name <name> --description <summary> --procedure <steps>',
221
- ].join('\n');
232
+ emptyMessage ? '' : ' No local Agent skills yet.',
233
+ ` ${emptyMessage ?? 'No Agent-local skills yet.'}`,
234
+ emptyMessage ? '' : ' Create one with: goodvibes-agent skills create --name <name> --description <summary> --procedure <steps>',
235
+ ].filter(Boolean).join('\n');
222
236
  }
223
237
  return [
224
238
  `${title} (${skills.length})`,
225
- ` store: ${path}`,
239
+ ` store ${path}`,
226
240
  ...skills.map(summarizeSkill),
227
241
  ].join('\n');
228
242
  }
229
243
 
230
244
  function summarizeDiscoveredSkill(skill: SkillRecord): string {
231
245
  const description = skill.description ? ` - ${skill.description}` : '';
232
- const dependencies = skill.dependencies.length > 0 ? ` deps=${skill.dependencies.join(',')}` : '';
233
- const includes = skill.includes.length > 0 ? ` includes=${skill.includes.join(',')}` : '';
246
+ const dependencies = skill.dependencies.length > 0 ? ` dependencies ${skill.dependencies.join(', ')}` : '';
247
+ const includes = skill.includes.length > 0 ? ` includes ${skill.includes.join(', ')}` : '';
234
248
  return [
235
249
  ` ${skill.name} ${skill.origin}${description}${dependencies}${includes}`,
236
- ` path: ${skill.path}`,
250
+ ` path ${skill.path}`,
237
251
  ].join('\n');
238
252
  }
239
253
 
@@ -273,29 +287,40 @@ function discoveredFrontmatterList(skill: SkillRecord, key: string): readonly st
273
287
  return value.split(',').map((entry) => entry.trim()).filter(Boolean);
274
288
  }
275
289
 
276
- function renderBundleList(title: string, path: string, bundles: readonly AgentSkillBundleRecord[]): string {
290
+ function discoveredFrontmatterAnyList(skill: SkillRecord, keys: readonly string[]): readonly string[] {
291
+ for (const key of keys) {
292
+ const values = discoveredFrontmatterList(skill, key);
293
+ if (values.length > 0) return values;
294
+ }
295
+ return [];
296
+ }
297
+
298
+ function renderBundleList(title: string, path: string, bundles: readonly AgentSkillBundleRecord[], skills: readonly AgentSkillRecord[], emptyMessage?: string): string {
277
299
  if (bundles.length === 0) {
278
300
  return [
279
301
  title,
280
- ' No local Agent skill bundles yet.',
281
- ' Create one with: goodvibes-agent skills bundle create --name <name> --description <summary> --skills <id,id>',
282
- ].join('\n');
302
+ ` ${emptyMessage ?? 'No Agent-local skill bundles yet.'}`,
303
+ emptyMessage ? '' : ' Create one with: goodvibes-agent skills bundle create --name <name> --description <summary> --skills <id,id>',
304
+ ].filter(Boolean).join('\n');
283
305
  }
284
306
  return [
285
307
  `${title} (${bundles.length})`,
286
- ` store: ${path}`,
287
- ...bundles.map(summarizeBundle),
308
+ ` store ${path}`,
309
+ ...bundles.map((bundle) => summarizeBundle(bundle, skills)),
288
310
  ].join('\n');
289
311
  }
290
312
 
291
313
  function renderSkill(skill: AgentSkillRecord): string {
314
+ const readiness = evaluateAgentSkillReadiness(skill);
292
315
  return [
293
316
  `Skill ${skill.name}`,
294
- ` id: ${skill.id}`,
317
+ ` id ${skill.id}`,
295
318
  ` enabled: ${skill.enabled ? 'yes' : 'no'}`,
296
- ` review: ${skill.reviewState}`,
297
- ` source: ${skill.source}`,
298
- ` provenance: ${skill.provenance}`,
319
+ ` readiness: ${readiness.ready ? 'ready' : 'needs setup'}`,
320
+ ` requirements: ${skill.requirements.map(formatAgentSkillRequirement).join(', ') || '(none)'}`,
321
+ readiness.missing.length > 0 ? ` missing: ${readiness.missing.map(formatAgentSkillRequirement).join(', ')}` : '',
322
+ ` review: ${formatAgentRecordReviewState(skill.reviewState)}`,
323
+ ` origin: ${formatAgentRecordOrigin(skill.source, skill.provenance)}`,
299
324
  ` tags: ${skill.tags.join(', ') || '(none)'}`,
300
325
  ` triggers: ${skill.triggers.join(', ') || '(manual)'}`,
301
326
  ` created: ${skill.createdAt}`,
@@ -308,14 +333,20 @@ function renderSkill(skill: AgentSkillRecord): string {
308
333
  ].filter((line): line is string => Boolean(line)).join('\n');
309
334
  }
310
335
 
311
- function renderBundle(bundle: AgentSkillBundleRecord): string {
336
+ function renderBundle(bundle: AgentSkillBundleRecord, skills: readonly AgentSkillRecord[]): string {
337
+ const readiness = evaluateAgentSkillBundleReadiness(bundle, skills);
338
+ const missing = [
339
+ ...readiness.missingRequirements.map(formatAgentSkillRequirement),
340
+ ...readiness.missingSkillIds.map((skillId) => `skill:${skillId}`),
341
+ ];
312
342
  return [
313
343
  `Skill bundle ${bundle.name}`,
314
- ` id: ${bundle.id}`,
344
+ ` id ${bundle.id}`,
315
345
  ` enabled: ${bundle.enabled ? 'yes' : 'no'}`,
316
- ` review: ${bundle.reviewState}`,
317
- ` source: ${bundle.source}`,
318
- ` provenance: ${bundle.provenance}`,
346
+ ` readiness: ${readiness.ready ? 'ready' : 'needs setup'}`,
347
+ missing.length > 0 ? ` missing: ${missing.join(', ')}` : '',
348
+ ` review: ${formatAgentRecordReviewState(bundle.reviewState)}`,
349
+ ` origin: ${formatAgentRecordOrigin(bundle.source, bundle.provenance)}`,
319
350
  ` skills: ${bundle.skillIds.join(', ')}`,
320
351
  ` created: ${bundle.createdAt}`,
321
352
  ` updated: ${bundle.updatedAt}`,
@@ -330,11 +361,11 @@ function usagePersonas(): string {
330
361
  }
331
362
 
332
363
  function usageSkills(): string {
333
- return 'Usage: goodvibes-agent skills [list|enabled|active|discover|import-discovered <name> --yes|search <query>|show <id>|create|update <id>|enable <id>|disable <id>|review <id>|stale <id> <reason>|delete <id> --yes|bundle ...]';
364
+ return 'Usage: goodvibes-agent skills [list|enabled|active|attention|discover|import-discovered <name> --yes|search <query>|show <id>|create [--requires-env A,B] [--requires-command gh,jq]|update <id>|enable <id>|disable <id>|review <id>|stale <id> <reason>|delete <id> --yes|bundle ...]';
334
365
  }
335
366
 
336
367
  function usageBundles(): string {
337
- return 'Usage: goodvibes-agent skills bundle [list|enabled|search <query>|show <id>|create|update <id>|enable <id>|disable <id>|review <id>|stale <id> <reason>|delete <id> --yes]';
368
+ return 'Usage: goodvibes-agent skills bundle [list|enabled|attention|search <query>|show <id>|create|update <id>|enable <id>|disable <id>|review <id>|stale <id> <reason>|delete <id> --yes]';
338
369
  }
339
370
 
340
371
  function errorOutput(runtime: CliCommandRuntime, error: unknown, kind: string): CliCommandOutput {
@@ -367,17 +398,17 @@ export async function handlePersonasCommand(runtime: CliCommandRuntime): Promise
367
398
  if (!name) return failure(runtime, 'invalid_persona_command', 'Usage: goodvibes-agent personas import-discovered <name> [--use] --yes', 2);
368
399
  const discovered = findDiscoveredPersona(await discoverPersonas(shellPaths(runtime)), name);
369
400
  if (!discovered) {
370
- return failure(runtime, 'persona_discovery_not_found', `Unknown discovered Agent persona: ${name}\nRun goodvibes-agent personas discover to inspect available persona files.`, 1);
401
+ return failure(runtime, 'persona_discovery_not_found', `Unknown discovered Agent persona ${name}\nRun goodvibes-agent personas discover to inspect available persona files.`, 1);
371
402
  }
372
403
  if (!hasFlag(options, 'yes')) {
373
404
  return success(runtime, 'agent.personas.import_discovered.preview', { persona: discovered }, [
374
405
  'Agent persona import preview',
375
- ` name: ${discovered.name}`,
376
- ` origin: ${discovered.origin}`,
377
- ` path: ${discovered.path}`,
378
- ` description: ${discovered.description || '(none)'}`,
379
- ` body characters: ${discovered.body.length}`,
380
- ' next: rerun with --yes to import into the Agent-local persona registry',
406
+ ` name ${discovered.name}`,
407
+ ` origin ${discovered.origin}`,
408
+ ` path ${discovered.path}`,
409
+ ` description ${discovered.description || '(none)'}`,
410
+ ` body characters ${discovered.body.length}`,
411
+ ' next rerun with --yes to import into the Agent-local persona registry',
381
412
  ].join('\n'));
382
413
  }
383
414
  const persona = registry.create({
@@ -390,7 +421,11 @@ export async function handlePersonasCommand(runtime: CliCommandRuntime): Promise
390
421
  provenance: `discovered:${discovered.origin}:${discovered.path}`,
391
422
  });
392
423
  if (hasFlag(options, 'use')) registry.setActive(persona.id);
393
- return success(runtime, 'agent.personas.import_discovered', persona, `Imported Agent persona ${persona.id}: ${persona.name}${hasFlag(options, 'use') ? ' (active)' : ''}`);
424
+ return success(runtime, 'agent.personas.import_discovered', persona, [
425
+ `Imported Agent persona ${persona.id}: ${persona.name}${hasFlag(options, 'use') ? ' (active)' : ''}`,
426
+ ` name ${persona.name}`,
427
+ ` active ${hasFlag(options, 'use') ? 'yes' : 'no'}`,
428
+ ].join('\n'));
394
429
  }
395
430
  if (normalized === 'search' || normalized === 'find') {
396
431
  const query = rest.join(' ').trim();
@@ -401,7 +436,7 @@ export async function handlePersonasCommand(runtime: CliCommandRuntime): Promise
401
436
  const id = rest[0];
402
437
  if (!id) return failure(runtime, 'invalid_persona_command', 'Usage: goodvibes-agent personas show <id>', 2);
403
438
  const persona = registry.get(id);
404
- if (!persona) return failure(runtime, 'persona_not_found', `Unknown Agent persona: ${id}`, 1);
439
+ if (!persona) return failure(runtime, 'persona_not_found', `Unknown Agent persona ${id}`, 1);
405
440
  return success(runtime, 'agent.personas.show', persona, renderPersona(persona, snapshot.activePersonaId));
406
441
  }
407
442
  if (normalized === 'create') {
@@ -412,10 +447,15 @@ export async function handlePersonasCommand(runtime: CliCommandRuntime): Promise
412
447
  body: requiredOption(options, 'body', 'Usage: goodvibes-agent personas create --name <name> --description <summary> --body <instructions>'),
413
448
  tags: csvOption(options, 'tags'),
414
449
  triggers: csvOption(options, 'triggers'),
415
- provenance: optionValue(options, 'provenance') ?? 'cli',
450
+ provenance: optionValue(options, 'provenance') ?? 'Command',
416
451
  });
417
452
  if (hasFlag(options, 'use')) registry.setActive(persona.id);
418
- return success(runtime, 'agent.personas.create', persona, `Agent persona created: ${persona.id}${hasFlag(options, 'use') ? ' (active)' : ''}`);
453
+ return success(runtime, 'agent.personas.create', persona, [
454
+ 'Agent persona created',
455
+ ` id ${persona.id}`,
456
+ hasFlag(options, 'use') ? ' (active)' : '',
457
+ ` active ${hasFlag(options, 'use') ? 'yes' : 'no'}`,
458
+ ].filter(Boolean).join('\n'));
419
459
  }
420
460
  if (normalized === 'update') {
421
461
  const id = rest[0];
@@ -429,13 +469,19 @@ export async function handlePersonasCommand(runtime: CliCommandRuntime): Promise
429
469
  triggers: csvOption(options, 'triggers'),
430
470
  provenance: optionValue(options, 'provenance'),
431
471
  });
432
- return success(runtime, 'agent.personas.update', persona, `Agent persona updated: ${persona.id}`);
472
+ return success(runtime, 'agent.personas.update', persona, [
473
+ 'Agent persona updated',
474
+ ` id ${persona.id}`,
475
+ ].join('\n'));
433
476
  }
434
477
  if (normalized === 'use') {
435
478
  const id = rest[0];
436
479
  if (!id) return failure(runtime, 'invalid_persona_command', 'Usage: goodvibes-agent personas use <id>', 2);
437
480
  const persona = registry.setActive(id);
438
- return success(runtime, 'agent.personas.use', persona, `Active Agent persona: ${persona.id}`);
481
+ return success(runtime, 'agent.personas.use', persona, [
482
+ 'Active Agent persona',
483
+ ` id ${persona.id}`,
484
+ ].join('\n'));
439
485
  }
440
486
  if (normalized === 'clear') {
441
487
  registry.clearActive();
@@ -445,13 +491,19 @@ export async function handlePersonasCommand(runtime: CliCommandRuntime): Promise
445
491
  const id = rest[0];
446
492
  if (!id) return failure(runtime, 'invalid_persona_command', 'Usage: goodvibes-agent personas review <id>', 2);
447
493
  const persona = registry.markReviewed(id);
448
- return success(runtime, 'agent.personas.review', persona, `Agent persona reviewed: ${persona.id}`);
494
+ return success(runtime, 'agent.personas.review', persona, [
495
+ 'Agent persona reviewed',
496
+ ` id ${persona.id}`,
497
+ ].join('\n'));
449
498
  }
450
499
  if (normalized === 'stale') {
451
500
  const id = rest[0];
452
501
  if (!id || rest.length < 2) return failure(runtime, 'invalid_persona_command', 'Usage: goodvibes-agent personas stale <id> <reason>', 2);
453
502
  const persona = registry.markStale(id, rest.slice(1).join(' '));
454
- return success(runtime, 'agent.personas.stale', persona, `Agent persona marked stale: ${persona.id}`);
503
+ return success(runtime, 'agent.personas.stale', persona, [
504
+ 'Agent persona marked stale',
505
+ ` id ${persona.id}`,
506
+ ].join('\n'));
455
507
  }
456
508
  if (normalized === 'delete' || normalized === 'remove' || normalized === 'rm') {
457
509
  const options = parseOptions(rest);
@@ -459,7 +511,10 @@ export async function handlePersonasCommand(runtime: CliCommandRuntime): Promise
459
511
  if (!id) return failure(runtime, 'invalid_persona_command', 'Usage: goodvibes-agent personas delete <id> --yes', 2);
460
512
  if (!hasFlag(options, 'yes')) return failure(runtime, 'confirmation_required', `Refusing to delete Agent persona ${id} without --yes.`, 2);
461
513
  const persona = registry.deletePersona(id);
462
- return success(runtime, 'agent.personas.delete', persona, `Agent persona deleted: ${persona.id}`);
514
+ return success(runtime, 'agent.personas.delete', persona, [
515
+ `Agent persona deleted: ${id}`,
516
+ ` id ${persona.id}`,
517
+ ].join('\n'));
463
518
  }
464
519
  return failure(runtime, 'invalid_persona_command', usagePersonas(), 2);
465
520
  } catch (error) {
@@ -473,6 +528,7 @@ function skillPayloadFromOptions(options: ParsedOptions): {
473
528
  readonly procedure: string;
474
529
  readonly tags: readonly string[] | undefined;
475
530
  readonly triggers: readonly string[] | undefined;
531
+ readonly requirements: ReturnType<typeof buildAgentSkillRequirements>;
476
532
  readonly enabled: boolean | undefined;
477
533
  readonly provenance: string;
478
534
  } {
@@ -483,8 +539,12 @@ function skillPayloadFromOptions(options: ParsedOptions): {
483
539
  procedure: requiredOption(options, 'procedure', usage),
484
540
  tags: csvOption(options, 'tags'),
485
541
  triggers: csvOption(options, 'triggers'),
542
+ requirements: buildAgentSkillRequirements({
543
+ env: csvOption(options, 'requires-env'),
544
+ commands: csvOption(options, 'requires-command') ?? csvOption(options, 'requires-commands'),
545
+ }),
486
546
  enabled: hasFlag(options, 'enabled') ? true : undefined,
487
- provenance: optionValue(options, 'provenance') ?? 'cli',
547
+ provenance: optionValue(options, 'provenance') ?? 'Command',
488
548
  };
489
549
  }
490
550
 
@@ -495,22 +555,26 @@ async function handleSkillBundleCommand(runtime: CliCommandRuntime, args: readon
495
555
  const registry = skillRegistry(runtime);
496
556
  const snapshot = registry.snapshot();
497
557
  if (normalized === 'list' || normalized === 'ls') {
498
- return success(runtime, 'agent.skills.bundles.list', { path: snapshot.path, bundles: snapshot.bundles }, renderBundleList('Agent skill bundles', snapshot.path, snapshot.bundles));
558
+ return success(runtime, 'agent.skills.bundles.list', { path: snapshot.path, bundles: snapshot.bundles }, renderBundleList('Agent skill bundles', snapshot.path, snapshot.bundles, snapshot.skills));
499
559
  }
500
560
  if (normalized === 'enabled') {
501
- return success(runtime, 'agent.skills.bundles.enabled', { path: snapshot.path, bundles: snapshot.enabledBundles }, renderBundleList('Enabled Agent skill bundles', snapshot.path, snapshot.enabledBundles));
561
+ return success(runtime, 'agent.skills.bundles.enabled', { path: snapshot.path, bundles: snapshot.enabledBundles }, renderBundleList('Enabled Agent skill bundles', snapshot.path, snapshot.enabledBundles, snapshot.skills));
562
+ }
563
+ if (normalized === 'attention' || normalized === 'needs-setup') {
564
+ const bundles = snapshot.bundles.filter((bundle) => !evaluateAgentSkillBundleReadiness(bundle, snapshot.skills).ready);
565
+ return success(runtime, 'agent.skills.bundles.attention', { path: snapshot.path, bundles }, renderBundleList('Agent skill bundles needing setup', snapshot.path, bundles, snapshot.skills, 'No Agent-local skill bundles need setup.'));
502
566
  }
503
567
  if (normalized === 'search' || normalized === 'find') {
504
568
  const query = rest.join(' ').trim();
505
569
  const results = registry.searchBundles(query);
506
- return success(runtime, 'agent.skills.bundles.search', { query, results }, renderBundleList(`Agent skill bundles matching "${query}"`, snapshot.path, results));
570
+ return success(runtime, 'agent.skills.bundles.search', { query, results }, renderBundleList(`Agent skill bundles matching "${query}"`, snapshot.path, results, snapshot.skills));
507
571
  }
508
572
  if (normalized === 'show' || normalized === 'get') {
509
573
  const id = rest[0];
510
574
  if (!id) return failure(runtime, 'invalid_skill_bundle_command', 'Usage: goodvibes-agent skills bundle show <id>', 2);
511
575
  const bundle = registry.getBundle(id);
512
- if (!bundle) return failure(runtime, 'skill_bundle_not_found', `Unknown Agent skill bundle: ${id}`, 1);
513
- return success(runtime, 'agent.skills.bundles.show', bundle, renderBundle(bundle));
576
+ if (!bundle) return failure(runtime, 'skill_bundle_not_found', `Unknown Agent skill bundle ${id}`, 1);
577
+ return success(runtime, 'agent.skills.bundles.show', bundle, renderBundle(bundle, snapshot.skills));
514
578
  }
515
579
  if (normalized === 'create') {
516
580
  const options = parseOptions(rest);
@@ -520,9 +584,12 @@ async function handleSkillBundleCommand(runtime: CliCommandRuntime, args: readon
520
584
  description: requiredOption(options, 'description', usage),
521
585
  skillIds: requiredOption(options, 'skills', usage).split(',').map((entry) => entry.trim()).filter(Boolean),
522
586
  enabled: hasFlag(options, 'enabled'),
523
- provenance: optionValue(options, 'provenance') ?? 'cli',
587
+ provenance: optionValue(options, 'provenance') ?? 'Command',
524
588
  });
525
- return success(runtime, 'agent.skills.bundles.create', bundle, `Agent skill bundle created: ${bundle.id}`);
589
+ return success(runtime, 'agent.skills.bundles.create', bundle, [
590
+ 'Agent skill bundle created',
591
+ ` id ${bundle.id}`,
592
+ ].join('\n'));
526
593
  }
527
594
  if (normalized === 'update') {
528
595
  const id = rest[0];
@@ -534,25 +601,37 @@ async function handleSkillBundleCommand(runtime: CliCommandRuntime, args: readon
534
601
  skillIds: csvOption(options, 'skills'),
535
602
  provenance: optionValue(options, 'provenance'),
536
603
  });
537
- return success(runtime, 'agent.skills.bundles.update', bundle, `Agent skill bundle updated: ${bundle.id}`);
604
+ return success(runtime, 'agent.skills.bundles.update', bundle, [
605
+ 'Agent skill bundle updated',
606
+ ` id ${bundle.id}`,
607
+ ].join('\n'));
538
608
  }
539
609
  if (normalized === 'enable' || normalized === 'disable') {
540
610
  const id = rest[0];
541
611
  if (!id) return failure(runtime, 'invalid_skill_bundle_command', `Usage: goodvibes-agent skills bundle ${normalized} <id>`, 2);
542
612
  const bundle = registry.setBundleEnabled(id, normalized === 'enable');
543
- return success(runtime, `agent.skills.bundles.${normalized}`, bundle, `Agent skill bundle ${normalized}d: ${bundle.id}`);
613
+ return success(runtime, `agent.skills.bundles.${normalized}`, bundle, [
614
+ `Agent skill bundle ${normalized === 'enable' ? 'enabled' : 'disabled'}`,
615
+ ` id ${bundle.id}`,
616
+ ].join('\n'));
544
617
  }
545
618
  if (normalized === 'review') {
546
619
  const id = rest[0];
547
620
  if (!id) return failure(runtime, 'invalid_skill_bundle_command', 'Usage: goodvibes-agent skills bundle review <id>', 2);
548
621
  const bundle = registry.markBundleReviewed(id);
549
- return success(runtime, 'agent.skills.bundles.review', bundle, `Agent skill bundle reviewed: ${bundle.id}`);
622
+ return success(runtime, 'agent.skills.bundles.review', bundle, [
623
+ 'Agent skill bundle reviewed',
624
+ ` id ${bundle.id}`,
625
+ ].join('\n'));
550
626
  }
551
627
  if (normalized === 'stale') {
552
628
  const id = rest[0];
553
629
  if (!id || rest.length < 2) return failure(runtime, 'invalid_skill_bundle_command', 'Usage: goodvibes-agent skills bundle stale <id> <reason>', 2);
554
630
  const bundle = registry.markBundleStale(id, rest.slice(1).join(' '));
555
- return success(runtime, 'agent.skills.bundles.stale', bundle, `Agent skill bundle marked stale: ${bundle.id}`);
631
+ return success(runtime, 'agent.skills.bundles.stale', bundle, [
632
+ 'Agent skill bundle marked stale',
633
+ ` id ${bundle.id}`,
634
+ ].join('\n'));
556
635
  }
557
636
  if (normalized === 'delete' || normalized === 'remove' || normalized === 'rm') {
558
637
  const options = parseOptions(rest);
@@ -560,7 +639,10 @@ async function handleSkillBundleCommand(runtime: CliCommandRuntime, args: readon
560
639
  if (!id) return failure(runtime, 'invalid_skill_bundle_command', 'Usage: goodvibes-agent skills bundle delete <id> --yes', 2);
561
640
  if (!hasFlag(options, 'yes')) return failure(runtime, 'confirmation_required', `Refusing to delete Agent skill bundle ${id} without --yes.`, 2);
562
641
  const bundle = registry.deleteBundle(id);
563
- return success(runtime, 'agent.skills.bundles.delete', bundle, `Agent skill bundle deleted: ${bundle.id}`);
642
+ return success(runtime, 'agent.skills.bundles.delete', bundle, [
643
+ 'Agent skill bundle deleted',
644
+ ` id ${bundle.id}`,
645
+ ].join('\n'));
564
646
  }
565
647
  return failure(runtime, 'invalid_skill_bundle_command', usageBundles(), 2);
566
648
  } catch (error) {
@@ -584,9 +666,13 @@ export async function handleSkillsCommand(runtime: CliCommandRuntime): Promise<C
584
666
  if (normalized === 'active') {
585
667
  return success(runtime, 'agent.skills.active', { path: snapshot.path, skills: snapshot.activeSkills, bundles: snapshot.enabledBundles }, [
586
668
  renderSkillList('Active Agent skills', snapshot.path, snapshot.activeSkills),
587
- snapshot.enabledBundles.length > 0 ? renderBundleList('Enabled Agent skill bundles', snapshot.path, snapshot.enabledBundles) : '',
669
+ snapshot.enabledBundles.length > 0 ? renderBundleList('Enabled Agent skill bundles', snapshot.path, snapshot.enabledBundles, snapshot.skills) : '',
588
670
  ].filter(Boolean).join('\n\n'));
589
671
  }
672
+ if (normalized === 'attention' || normalized === 'needs-setup') {
673
+ const skills = snapshot.skills.filter((skill) => !evaluateAgentSkillReadiness(skill).ready);
674
+ return success(runtime, 'agent.skills.attention', { path: snapshot.path, skills }, renderSkillList('Agent skills needing setup', snapshot.path, skills, 'No Agent-local skills need setup.'));
675
+ }
590
676
  if (normalized === 'discover') {
591
677
  const discovered = await discoverSkills(shellPaths(runtime));
592
678
  return success(runtime, 'agent.skills.discover', { skills: discovered }, renderDiscoveredSkillList(discovered));
@@ -597,17 +683,17 @@ export async function handleSkillsCommand(runtime: CliCommandRuntime): Promise<C
597
683
  if (!name) return failure(runtime, 'invalid_skill_command', 'Usage: goodvibes-agent skills import-discovered <name> [--enabled] --yes', 2);
598
684
  const discovered = findDiscoveredSkill(await discoverSkills(shellPaths(runtime)), name);
599
685
  if (!discovered) {
600
- return failure(runtime, 'skill_discovery_not_found', `Unknown discovered Agent skill: ${name}\nRun goodvibes-agent skills discover to inspect available skill files.`, 1);
686
+ return failure(runtime, 'skill_discovery_not_found', `Unknown discovered Agent skill ${name}\nRun goodvibes-agent skills discover to inspect available skill files.`, 1);
601
687
  }
602
688
  if (!hasFlag(options, 'yes')) {
603
689
  return success(runtime, 'agent.skills.import_discovered.preview', { skill: discovered }, [
604
690
  'Agent skill import preview',
605
- ` name: ${discovered.name}`,
606
- ` origin: ${discovered.origin}`,
607
- ` path: ${discovered.path}`,
608
- ` description: ${discovered.description || '(none)'}`,
609
- ` procedure characters: ${discovered.body.length}`,
610
- ' next: rerun with --yes to import into the Agent-local skill registry',
691
+ ` name ${discovered.name}`,
692
+ ` origin ${discovered.origin}`,
693
+ ` path ${discovered.path}`,
694
+ ` description ${discovered.description || '(none)'}`,
695
+ ` procedure characters ${discovered.body.length}`,
696
+ ' next rerun with --yes to import into the Agent-local skill registry',
611
697
  ].join('\n'));
612
698
  }
613
699
  const skill = registry.create({
@@ -616,11 +702,19 @@ export async function handleSkillsCommand(runtime: CliCommandRuntime): Promise<C
616
702
  procedure: discovered.body,
617
703
  tags: discoveredFrontmatterList(discovered, 'tags'),
618
704
  triggers: discoveredFrontmatterList(discovered, 'triggers'),
705
+ requirements: buildAgentSkillRequirements({
706
+ env: discoveredFrontmatterAnyList(discovered, ['requiresEnv', 'requires-env', 'requires_env']),
707
+ commands: discoveredFrontmatterAnyList(discovered, ['requiresCommands', 'requires-commands', 'requires_commands', 'commands']),
708
+ }),
619
709
  enabled: hasFlag(options, 'enabled'),
620
710
  source: 'imported',
621
711
  provenance: `discovered:${discovered.origin}:${discovered.path}`,
622
712
  });
623
- return success(runtime, 'agent.skills.import_discovered', skill, `Imported Agent skill ${skill.id}: ${skill.name}${skill.enabled ? ' (enabled)' : ''}`);
713
+ return success(runtime, 'agent.skills.import_discovered', skill, [
714
+ `Imported Agent skill ${skill.id}: ${skill.name}${skill.enabled ? ' (enabled)' : ''}`,
715
+ ` name ${skill.name}`,
716
+ ` enabled ${skill.enabled ? 'yes' : 'no'}`,
717
+ ].join('\n'));
624
718
  }
625
719
  if (normalized === 'search' || normalized === 'find') {
626
720
  const query = rest.join(' ').trim();
@@ -631,12 +725,17 @@ export async function handleSkillsCommand(runtime: CliCommandRuntime): Promise<C
631
725
  const id = rest[0];
632
726
  if (!id) return failure(runtime, 'invalid_skill_command', 'Usage: goodvibes-agent skills show <id>', 2);
633
727
  const skill = registry.get(id);
634
- if (!skill) return failure(runtime, 'skill_not_found', `Unknown Agent skill: ${id}`, 1);
728
+ if (!skill) return failure(runtime, 'skill_not_found', `Unknown Agent skill ${id}`, 1);
635
729
  return success(runtime, 'agent.skills.show', skill, renderSkill(skill));
636
730
  }
637
731
  if (normalized === 'create') {
638
732
  const skill = registry.create(skillPayloadFromOptions(parseOptions(rest)));
639
- return success(runtime, 'agent.skills.create', skill, `Agent skill created: ${skill.id}${skill.enabled ? ' (enabled)' : ''}`);
733
+ return success(runtime, 'agent.skills.create', skill, [
734
+ 'Agent skill created',
735
+ ` id ${skill.id}`,
736
+ skill.enabled ? ' (enabled)' : '',
737
+ ` enabled ${skill.enabled ? 'yes' : 'no'}`,
738
+ ].filter(Boolean).join('\n'));
640
739
  }
641
740
  if (normalized === 'update') {
642
741
  const id = rest[0];
@@ -648,27 +747,45 @@ export async function handleSkillsCommand(runtime: CliCommandRuntime): Promise<C
648
747
  procedure: optionValue(options, 'procedure'),
649
748
  tags: csvOption(options, 'tags'),
650
749
  triggers: csvOption(options, 'triggers'),
750
+ requirements: options.values.has('requires-env') || options.values.has('requires-command') || options.values.has('requires-commands')
751
+ ? buildAgentSkillRequirements({
752
+ env: csvOption(options, 'requires-env'),
753
+ commands: csvOption(options, 'requires-command') ?? csvOption(options, 'requires-commands'),
754
+ })
755
+ : undefined,
651
756
  provenance: optionValue(options, 'provenance'),
652
757
  });
653
- return success(runtime, 'agent.skills.update', skill, `Agent skill updated: ${skill.id}`);
758
+ return success(runtime, 'agent.skills.update', skill, [
759
+ 'Agent skill updated',
760
+ ` id ${skill.id}`,
761
+ ].join('\n'));
654
762
  }
655
763
  if (normalized === 'enable' || normalized === 'disable') {
656
764
  const id = rest[0];
657
765
  if (!id) return failure(runtime, 'invalid_skill_command', `Usage: goodvibes-agent skills ${normalized} <id>`, 2);
658
766
  const skill = registry.setEnabled(id, normalized === 'enable');
659
- return success(runtime, `agent.skills.${normalized}`, skill, `Agent skill ${normalized}d: ${skill.id}`);
767
+ return success(runtime, `agent.skills.${normalized}`, skill, [
768
+ `Agent skill ${normalized === 'enable' ? 'enabled' : 'disabled'}`,
769
+ ` id ${skill.id}`,
770
+ ].join('\n'));
660
771
  }
661
772
  if (normalized === 'review') {
662
773
  const id = rest[0];
663
774
  if (!id) return failure(runtime, 'invalid_skill_command', 'Usage: goodvibes-agent skills review <id>', 2);
664
775
  const skill = registry.markReviewed(id);
665
- return success(runtime, 'agent.skills.review', skill, `Agent skill reviewed: ${skill.id}`);
776
+ return success(runtime, 'agent.skills.review', skill, [
777
+ 'Agent skill reviewed',
778
+ ` id ${skill.id}`,
779
+ ].join('\n'));
666
780
  }
667
781
  if (normalized === 'stale') {
668
782
  const id = rest[0];
669
783
  if (!id || rest.length < 2) return failure(runtime, 'invalid_skill_command', 'Usage: goodvibes-agent skills stale <id> <reason>', 2);
670
784
  const skill = registry.markStale(id, rest.slice(1).join(' '));
671
- return success(runtime, 'agent.skills.stale', skill, `Agent skill marked stale: ${skill.id}`);
785
+ return success(runtime, 'agent.skills.stale', skill, [
786
+ 'Agent skill marked stale',
787
+ ` id ${skill.id}`,
788
+ ].join('\n'));
672
789
  }
673
790
  if (normalized === 'delete' || normalized === 'remove' || normalized === 'rm') {
674
791
  const options = parseOptions(rest);
@@ -676,7 +793,10 @@ export async function handleSkillsCommand(runtime: CliCommandRuntime): Promise<C
676
793
  if (!id) return failure(runtime, 'invalid_skill_command', 'Usage: goodvibes-agent skills delete <id> --yes', 2);
677
794
  if (!hasFlag(options, 'yes')) return failure(runtime, 'confirmation_required', `Refusing to delete Agent skill ${id} without --yes.`, 2);
678
795
  const skill = registry.deleteSkill(id);
679
- return success(runtime, 'agent.skills.delete', skill, `Agent skill deleted: ${skill.id}`);
796
+ return success(runtime, 'agent.skills.delete', skill, [
797
+ `Agent skill deleted: ${id}`,
798
+ ` id ${skill.id}`,
799
+ ].join('\n'));
680
800
  }
681
801
  return failure(runtime, 'invalid_skill_command', usageSkills(), 2);
682
802
  } catch (error) {