@@ -30,6 +30,9 @@ public class CliCommandsPanel extends JPanel {
3030 private JButton removeButton ;
3131 private JTextField nameField ;
3232 private JTextField descriptionField ;
33+ private JCheckBox updaterCheckbox ;
34+ private JCheckBox launcherCheckbox ;
35+ private JCheckBox serviceControllerCheckbox ;
3336 private JTextArea argsField ;
3437 private JLabel validationLabel ;
3538 private ActionListener changeListener ;
@@ -179,6 +182,56 @@ private JPanel createRightPanel() {
179182
180183 formPanel .add (Box .createVerticalStrut (10 ));
181184
185+ // Implements field (checkboxes)
186+ JPanel implLabelPanel = new JPanel ();
187+ implLabelPanel .setOpaque (false );
188+ implLabelPanel .setLayout (new BoxLayout (implLabelPanel , BoxLayout .X_AXIS ));
189+ JLabel implLabel = new JLabel ("Implements" );
190+ implLabel .setFont (implLabel .getFont ().deriveFont (Font .BOLD ));
191+ implLabelPanel .add (implLabel );
192+ implLabelPanel .add (Box .createHorizontalStrut (5 ));
193+ implLabelPanel .add (createInfoIcon ("<html>Special behaviors for this command.<br>See individual checkbox tooltips for details.</html>" ));
194+ implLabelPanel .add (Box .createHorizontalGlue ());
195+ implLabelPanel .setAlignmentX (Component .LEFT_ALIGNMENT );
196+ implLabelPanel .setMaximumSize (new Dimension (Integer .MAX_VALUE , implLabel .getPreferredSize ().height ));
197+ formPanel .add (implLabelPanel );
198+
199+ // Checkboxes panel
200+ JPanel checkboxPanel = new JPanel ();
201+ checkboxPanel .setOpaque (false );
202+ checkboxPanel .setLayout (new FlowLayout (FlowLayout .LEFT , 0 , 0 ));
203+ checkboxPanel .setAlignmentX (Component .LEFT_ALIGNMENT );
204+
205+ updaterCheckbox = new JCheckBox ("Updater" );
206+ updaterCheckbox .setOpaque (false );
207+ updaterCheckbox .setToolTipText ("<html>Intercepts 'update' argument to trigger app updates.<br>" +
208+ "Example: <code>myapp-cli update</code> → calls launcher with --jdeploy:update</html>" );
209+ updaterCheckbox .addActionListener (e -> onFieldChanged ());
210+ checkboxPanel .add (updaterCheckbox );
211+ checkboxPanel .add (Box .createHorizontalStrut (15 ));
212+
213+ launcherCheckbox = new JCheckBox ("Launcher" );
214+ launcherCheckbox .setOpaque (false );
215+ launcherCheckbox .setToolTipText ("<html>Launches the desktop GUI application.<br>" +
216+ "Arguments are passed as file paths or URLs to open.<br>" +
217+ "macOS: Uses 'open -a MyApp.app', others call binary directly.</html>" );
218+ launcherCheckbox .addActionListener (e -> onFieldChanged ());
219+ checkboxPanel .add (launcherCheckbox );
220+ checkboxPanel .add (Box .createHorizontalStrut (15 ));
221+
222+ serviceControllerCheckbox = new JCheckBox ("Service Controller" );
223+ serviceControllerCheckbox .setOpaque (false );
224+ serviceControllerCheckbox .setToolTipText ("<html>Intercepts 'service' as first argument for daemon control.<br>" +
225+ "Example: <code>myappctl service start</code> → calls launcher with --jdeploy:service start</html>" );
226+ serviceControllerCheckbox .addActionListener (e -> onFieldChanged ());
227+ checkboxPanel .add (serviceControllerCheckbox );
228+
229+ // Constrain the height of the checkbox panel to prevent vertical expansion
230+ checkboxPanel .setMaximumSize (new Dimension (Integer .MAX_VALUE , updaterCheckbox .getPreferredSize ().height ));
231+ formPanel .add (checkboxPanel );
232+
233+ formPanel .add (Box .createVerticalStrut (10 ));
234+
182235 // Arguments field
183236 JPanel argsLabelPanel = new JPanel ();
184237 argsLabelPanel .setOpaque (false );
@@ -312,6 +365,9 @@ public void load(JSONObject jdeploy) {
312365 commandsModel .clear ();
313366 nameField .setText ("" );
314367 descriptionField .setText ("" );
368+ updaterCheckbox .setSelected (false );
369+ launcherCheckbox .setSelected (false );
370+ serviceControllerCheckbox .setSelected (false );
315371 argsField .setForeground (Color .GRAY );
316372 argsField .setText (ARGS_PLACEHOLDER );
317373 validationLabel .setText (" " );
@@ -434,6 +490,9 @@ private void onCommandSelected(ListSelectionEvent evt) {
434490 if (index < 0 ) {
435491 nameField .setText ("" );
436492 descriptionField .setText ("" );
493+ updaterCheckbox .setSelected (false );
494+ launcherCheckbox .setSelected (false );
495+ serviceControllerCheckbox .setSelected (false );
437496 argsField .setForeground (Color .GRAY );
438497 argsField .setText (ARGS_PLACEHOLDER );
439498 validationLabel .setText (" " );
@@ -453,12 +512,31 @@ private void loadCommandForEditing(String commandName) {
453512 isUpdatingUI = true ;
454513 try {
455514 nameField .setText (commandName );
456-
515+
457516 // Load description and args from the backing data model
458517 JSONObject spec = commandsModel .get (commandName );
459518 if (spec != null ) {
460519 descriptionField .setText (spec .optString ("description" , "" ));
461-
520+
521+ // Load implements array
522+ updaterCheckbox .setSelected (false );
523+ launcherCheckbox .setSelected (false );
524+ serviceControllerCheckbox .setSelected (false );
525+
526+ if (spec .has ("implements" )) {
527+ JSONArray implArray = spec .getJSONArray ("implements" );
528+ for (int i = 0 ; i < implArray .length (); i ++) {
529+ String impl = implArray .getString (i );
530+ if (CommandSpecParser .IMPL_UPDATER .equals (impl )) {
531+ updaterCheckbox .setSelected (true );
532+ } else if (CommandSpecParser .IMPL_LAUNCHER .equals (impl )) {
533+ launcherCheckbox .setSelected (true );
534+ } else if (CommandSpecParser .IMPL_SERVICE_CONTROLLER .equals (impl )) {
535+ serviceControllerCheckbox .setSelected (true );
536+ }
537+ }
538+ }
539+
462540 // Load args - join array elements with newlines
463541 if (spec .has ("args" )) {
464542 JSONArray argsArray = spec .getJSONArray ("args" );
@@ -475,10 +553,13 @@ private void loadCommandForEditing(String commandName) {
475553 }
476554 } else {
477555 descriptionField .setText ("" );
556+ updaterCheckbox .setSelected (false );
557+ launcherCheckbox .setSelected (false );
558+ serviceControllerCheckbox .setSelected (false );
478559 argsField .setForeground (Color .GRAY );
479560 argsField .setText (ARGS_PLACEHOLDER );
480561 }
481-
562+
482563 validationLabel .setText (" " );
483564 } finally {
484565 isUpdatingUI = false ;
@@ -537,15 +618,32 @@ private void onNameChanged() {
537618 if (spec == null ) {
538619 spec = new JSONObject ();
539620 }
540-
621+
541622 // Save current form values into the spec before moving it
542623 String desc = descriptionField .getText ().trim ();
543624 if (!desc .isEmpty ()) {
544625 spec .put ("description" , desc );
545626 } else {
546627 spec .remove ("description" );
547628 }
548-
629+
630+ // Save implements array
631+ JSONArray implArray = new JSONArray ();
632+ if (updaterCheckbox .isSelected ()) {
633+ implArray .put (CommandSpecParser .IMPL_UPDATER );
634+ }
635+ if (launcherCheckbox .isSelected ()) {
636+ implArray .put (CommandSpecParser .IMPL_LAUNCHER );
637+ }
638+ if (serviceControllerCheckbox .isSelected ()) {
639+ implArray .put (CommandSpecParser .IMPL_SERVICE_CONTROLLER );
640+ }
641+ if (implArray .length () > 0 ) {
642+ spec .put ("implements" , implArray );
643+ } else {
644+ spec .remove ("implements" );
645+ }
646+
549647 String argsText = argsField .getText ().trim ();
550648 boolean isPlaceholder = argsText .equals (ARGS_PLACEHOLDER .trim ()) && argsField .getForeground ().equals (Color .GRAY );
551649
@@ -566,7 +664,7 @@ private void onNameChanged() {
566664 } else {
567665 spec .remove ("args" );
568666 }
569-
667+
570668 commandsModel .put (name , spec );
571669
572670 // Update the list (this will not trigger selection change)
@@ -605,6 +703,9 @@ private void removeSelectedCommand() {
605703 removeButton .setEnabled (false );
606704 nameField .setText ("" );
607705 descriptionField .setText ("" );
706+ updaterCheckbox .setSelected (false );
707+ launcherCheckbox .setSelected (false );
708+ serviceControllerCheckbox .setSelected (false );
608709 argsField .setForeground (Color .GRAY );
609710 argsField .setText (ARGS_PLACEHOLDER );
610711 validationLabel .setText (" " );
@@ -631,6 +732,21 @@ private JSONObject buildCommandSpec(String name) {
631732 spec .put ("description" , desc );
632733 }
633734
735+ // Build implements array
736+ JSONArray implArray = new JSONArray ();
737+ if (updaterCheckbox .isSelected ()) {
738+ implArray .put (CommandSpecParser .IMPL_UPDATER );
739+ }
740+ if (launcherCheckbox .isSelected ()) {
741+ implArray .put (CommandSpecParser .IMPL_LAUNCHER );
742+ }
743+ if (serviceControllerCheckbox .isSelected ()) {
744+ implArray .put (CommandSpecParser .IMPL_SERVICE_CONTROLLER );
745+ }
746+ if (implArray .length () > 0 ) {
747+ spec .put ("implements" , implArray );
748+ }
749+
634750 String argsText = argsField .getText ().trim ();
635751 // Don't save placeholder text as actual args
636752 boolean isPlaceholder = argsText .equals (ARGS_PLACEHOLDER .trim ()) && argsField .getForeground ().equals (Color .GRAY );
@@ -670,15 +786,32 @@ private void onFieldChanged() {
670786 spec = new JSONObject ();
671787 commandsModel .put (currentName , spec );
672788 }
673-
789+
674790 // Update description
675791 String desc = descriptionField .getText ().trim ();
676792 if (!desc .isEmpty ()) {
677793 spec .put ("description" , desc );
678794 } else {
679795 spec .remove ("description" );
680796 }
681-
797+
798+ // Update implements array
799+ JSONArray implArray = new JSONArray ();
800+ if (updaterCheckbox .isSelected ()) {
801+ implArray .put (CommandSpecParser .IMPL_UPDATER );
802+ }
803+ if (launcherCheckbox .isSelected ()) {
804+ implArray .put (CommandSpecParser .IMPL_LAUNCHER );
805+ }
806+ if (serviceControllerCheckbox .isSelected ()) {
807+ implArray .put (CommandSpecParser .IMPL_SERVICE_CONTROLLER );
808+ }
809+ if (implArray .length () > 0 ) {
810+ spec .put ("implements" , implArray );
811+ } else {
812+ spec .remove ("implements" );
813+ }
814+
682815 // Update args
683816 String argsText = argsField .getText ().trim ();
684817 boolean isPlaceholder = argsText .equals (ARGS_PLACEHOLDER .trim ()) && argsField .getForeground ().equals (Color .GRAY );
0 commit comments