2323 get_package_version ,
2424 is_newer_version ,
2525)
26+ from poly .cli_commands .skills import node_gate_reason , update_skills
2627from poly .constants import POLY_HOME_DIR
2728from poly .output .json_output import json_print
2829
@@ -68,13 +69,15 @@ def add_arguments(cls, subparsers: _SubParsersAction[ArgumentParser], parents: P
6869 "update" ,
6970 parents = [parents .verbose , parents .debug , parents .json ],
7071 formatter_class = RawTextHelpFormatter ,
71- help = "Update the Poly CLI to the latest version." ,
72+ help = "Update the Poly CLI and its AI agent skills to the latest version." ,
7273 description = (
73- "Update the Poly CLI to the latest version.\n \n "
74+ "Update the Poly CLI and its AI agent skills to the latest version.\n \n "
7475 "Examples:\n "
7576 " poly update\n "
7677 " poly update --check\n "
7778 " poly update --to 0.52.0\n "
79+ " poly update --cli-only\n "
80+ " poly update --skills-only\n "
7881 "\n "
7982 "The CLI also notices new releases on its own, at most once every 12\n "
8083 "hours. Set POLY_NO_UPDATE_CHECK=1 to silence that; it is already\n "
@@ -96,16 +99,58 @@ def add_arguments(cls, subparsers: _SubParsersAction[ArgumentParser], parents: P
9699 help = "Install a specific version instead of the latest, e.g. --to 0.52.0." ,
97100 )
98101
102+ scope_group = update_parser .add_mutually_exclusive_group ()
103+ scope_group .add_argument (
104+ "--cli-only" ,
105+ action = "store_true" ,
106+ help = "Update the CLI only, skipping the AI agent skills." ,
107+ )
108+ scope_group .add_argument (
109+ "--skills-only" ,
110+ action = "store_true" ,
111+ help = "Update the AI agent skills only, skipping the CLI." ,
112+ )
113+
99114 @classmethod
100115 def run (cls , args : Namespace ) -> None :
101116 """Run the update command."""
102- cls .update (args .check , args .json , args .to )
117+ cls .update (
118+ args .check ,
119+ args .json ,
120+ args .to ,
121+ cli_only = args .cli_only ,
122+ skills_only = args .skills_only ,
123+ )
103124
104125 @classmethod
105- def update (cls , check : bool , output_json : bool , target_version : str | None = None ) -> None :
106- """Update the Poly CLI to the latest version, or to ``target_version`` if given."""
126+ def update (
127+ cls ,
128+ check : bool ,
129+ output_json : bool ,
130+ target_version : str | None = None ,
131+ cli_only : bool = False ,
132+ skills_only : bool = False ,
133+ ) -> None :
134+ """Update the Poly CLI (and its AI agent skills) to the latest version.
135+
136+ With ``target_version``, install that CLI version instead of the latest.
137+ ``cli_only`` and ``skills_only`` narrow the update to one half; the
138+ skills half never applies to ``--check``, which is a CLI version check.
139+ """
107140 from poly .output .console import info , success
108141
142+ if skills_only :
143+ # Deliberately not gated on refuse_if_not_upgradable: an editable/dev
144+ # install cannot upgrade the CLI in place, but its skills still can be.
145+ updated = cls .update_skills_step (output_json , required = True )
146+ if output_json :
147+ json_print ({"success" : updated , "skills_updated" : updated })
148+ elif updated :
149+ success ("AI agent skills updated." )
150+ if not updated :
151+ sys .exit (1 )
152+ return
153+
109154 # Refuse before hitting the network or announcing anything, so an install we
110155 # cannot upgrade is not told that an update is on the way.
111156 if cls .refuse_if_not_upgradable (output_json ):
@@ -122,8 +167,16 @@ def update(cls, check: bool, output_json: bool, target_version: str | None = Non
122167 else :
123168 update_available , target = cls .check_for_updates ()
124169 if not update_available :
170+ # The CLI is current, but the skills may not be — the default
171+ # update still refreshes them.
172+ skills_updated = None
173+ if not check and not cli_only :
174+ skills_updated = cls .update_skills_step (output_json , required = False )
125175 if output_json :
126- json_print ({"update_available" : False , "latest_version" : target })
176+ result = {"update_available" : False , "latest_version" : target }
177+ if skills_updated is not None :
178+ result ["skills_updated" ] = skills_updated
179+ json_print (result )
127180 else :
128181 info ("Poly CLI is already up to date, no update needed." )
129182 info (f"Current version: { get_package_version ()} " )
@@ -140,11 +193,56 @@ def update(cls, check: bool, output_json: bool, target_version: str | None = Non
140193 info (f"Updating Poly CLI to version { target } ..." )
141194 if not cls .perform_update (output_json , target_version ):
142195 return
196+ skills_updated = None
197+ if not cli_only :
198+ skills_updated = cls .update_skills_step (output_json , required = False )
143199 if output_json :
144- json_print ({"success" : True , "latest_version" : target })
200+ result = {"success" : True , "latest_version" : target }
201+ if skills_updated is not None :
202+ result ["skills_updated" ] = skills_updated
203+ json_print (result )
145204 else :
146205 success (f"Poly CLI updated to version { target } ." )
147206
207+ @classmethod
208+ def update_skills_step (cls , output_json : bool , required : bool ) -> bool :
209+ """Update installed AI agent skills via the pinned ``npx skills`` package.
210+
211+ Args:
212+ output_json: Keep stdout machine-readable — npx output is captured
213+ and no console messages are printed.
214+ required: The skills are the whole point of the invocation
215+ (``--skills-only``): report failure as an error instead of a
216+ warning. Never exits — the caller owns the exit code.
217+
218+ Returns:
219+ True if the skills were updated.
220+ """
221+ from poly .output .console import error , info , warning
222+
223+ gate_reason = node_gate_reason ()
224+ if gate_reason :
225+ message = f"Cannot update AI agent skills: { gate_reason } ."
226+ if output_json :
227+ # The caller folds the failure into its own JSON output.
228+ logger .debug (message )
229+ elif required :
230+ error (message )
231+ else :
232+ warning (f"{ message } Skipping." )
233+ return False
234+
235+ if not output_json :
236+ info ("Updating AI agent skills..." )
237+ updated = update_skills (quiet = output_json )
238+ if not updated and not output_json :
239+ message = "AI agent skill update failed."
240+ if required :
241+ error (message )
242+ else :
243+ warning (f"{ message } Run 'poly update --skills-only' to retry." )
244+ return updated
245+
148246 @staticmethod
149247 def check_for_updates () -> tuple [bool , str ]:
150248 """Check whether a newer Poly CLI release is available on PyPI.
0 commit comments