gbos 1.3.21 → 1.3.22

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gbos",
3
- "version": "1.3.21",
3
+ "version": "1.3.22",
4
4
  "description": "GBOS - Command line interface for GBOS services",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -6,7 +6,7 @@ const program = new Command();
6
6
  const authCommand = require('./commands/auth');
7
7
  const connectCommand = require('./commands/connect');
8
8
  const logoutCommand = require('./commands/logout');
9
- const { tasksCommand, nextTaskCommand, continueCommand, fallbackCommand, addTaskCommand } = require('./commands/tasks');
9
+ const { tasksCommand, nextTaskCommand, continueCommand, fallbackCommand, addTaskCommand, completedCommand } = require('./commands/tasks');
10
10
  const { syncStartCommand, syncStopCommand, syncStatusCommand, syncNowCommand, repoCreateCommand, repoListCommand, repoCloneCommand } = require('./commands/gitlab');
11
11
  const { registryLoginCommand, registryImagesCommand, registryPushCommand, registryPullCommand } = require('./commands/registry');
12
12
  const config = require('./lib/config');
@@ -120,6 +120,13 @@ program
120
120
  .description('Cancel work from the current task and revert to last completed state')
121
121
  .action(fallbackCommand);
122
122
 
123
+ program
124
+ .command('completed')
125
+ .alias('done')
126
+ .description('Complete current task: commit, push to GitLab (creates repo if needed), and mark task done')
127
+ .option('-m, --message <message>', 'Custom commit message')
128
+ .action(completedCommand);
129
+
123
130
  program
124
131
  .command('add_task')
125
132
  .alias('add')
@@ -237,7 +244,7 @@ program
237
244
  cmd.outputHelp();
238
245
  } else {
239
246
  console.log(`Unknown command: ${command}`);
240
- console.log('Available commands: auth, connect, disconnect, status, tasks, next, continue, fallback, add_task, logout, gitlab, registry, help');
247
+ console.log('Available commands: auth, connect, disconnect, status, tasks, next, continue, completed, fallback, add_task, logout, gitlab, registry, help');
241
248
  }
242
249
  } else {
243
250
  program.outputHelp();
@@ -2,6 +2,9 @@ const api = require('../lib/api');
2
2
  const config = require('../lib/config');
3
3
  const { displayMessageBox, printBanner, printStatusTable, fg, LOGO_LIGHT, LOGO_PURPLE, RESET, BOLD, DIM, getTerminalWidth } = require('../lib/display');
4
4
  const readline = require('readline');
5
+ const { exec, spawn } = require('child_process');
6
+ const path = require('path');
7
+ const fs = require('fs');
5
8
 
6
9
  // Colors for prompts
7
10
  const CYAN = '\x1b[36m';
@@ -697,11 +700,265 @@ async function addTaskCommand() {
697
700
  }
698
701
  }
699
702
 
703
+ // Execute git command
704
+ function execGit(args, cwd = process.cwd()) {
705
+ return new Promise((resolve, reject) => {
706
+ exec(`git ${args}`, { cwd, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
707
+ if (error) {
708
+ reject(new Error(stderr || error.message));
709
+ } else {
710
+ resolve(stdout.trim());
711
+ }
712
+ });
713
+ });
714
+ }
715
+
716
+ // Get GitLab URL and token from session or env
717
+ function getGitLabConfig() {
718
+ const session = config.loadSession();
719
+ return {
720
+ url: session?.gitlab_url || process.env.GITLAB_URL || 'https://gitlab.com',
721
+ token: session?.gitlab_token || process.env.GITLAB_TOKEN || null,
722
+ };
723
+ }
724
+
725
+ // Completed command - commit, push, and complete task
726
+ async function completedCommand(options) {
727
+ const cwd = process.cwd();
728
+ const dirName = path.basename(cwd);
729
+
730
+ console.log(`\n${DIM}Processing completion...${RESET}\n`);
731
+
732
+ // Step 1: Check if current directory is a git repo
733
+ let isGitRepo = false;
734
+ let hasRemote = false;
735
+ let remoteUrl = '';
736
+
737
+ try {
738
+ await execGit('rev-parse --is-inside-work-tree', cwd);
739
+ isGitRepo = true;
740
+ console.log(` ${GREEN}✓${RESET} Git repository detected`);
741
+
742
+ // Check for remote
743
+ try {
744
+ remoteUrl = await execGit('remote get-url origin', cwd);
745
+ hasRemote = true;
746
+ console.log(` ${GREEN}✓${RESET} Remote: ${remoteUrl}`);
747
+ } catch (e) {
748
+ hasRemote = false;
749
+ console.log(` ${YELLOW}!${RESET} No remote configured`);
750
+ }
751
+ } catch (e) {
752
+ isGitRepo = false;
753
+ console.log(` ${YELLOW}!${RESET} Not a git repository`);
754
+ }
755
+
756
+ // Step 2: Initialize git if needed
757
+ if (!isGitRepo) {
758
+ console.log(`\n ${DIM}Initializing git repository...${RESET}`);
759
+ try {
760
+ await execGit('init', cwd);
761
+ isGitRepo = true;
762
+ console.log(` ${GREEN}✓${RESET} Git repository initialized`);
763
+ } catch (e) {
764
+ displayMessageBox('Error', `Failed to initialize git: ${e.message}`, 'error');
765
+ process.exit(1);
766
+ }
767
+ }
768
+
769
+ // Step 3: Create GitLab repo if no remote
770
+ if (!hasRemote) {
771
+ const gitlab = getGitLabConfig();
772
+
773
+ if (!gitlab.token) {
774
+ console.log(`\n ${YELLOW}!${RESET} No GitLab token configured.`);
775
+ console.log(` ${DIM}Set GITLAB_TOKEN environment variable to auto-create repos.${RESET}`);
776
+ console.log(` ${DIM}Skipping remote setup...${RESET}\n`);
777
+ } else {
778
+ console.log(`\n ${DIM}Creating GitLab repository "${dirName}"...${RESET}`);
779
+
780
+ try {
781
+ const response = await fetch(`${gitlab.url}/api/v4/projects`, {
782
+ method: 'POST',
783
+ headers: {
784
+ 'PRIVATE-TOKEN': gitlab.token,
785
+ 'Content-Type': 'application/json',
786
+ },
787
+ body: JSON.stringify({
788
+ name: dirName,
789
+ visibility: 'private',
790
+ initialize_with_readme: false,
791
+ }),
792
+ });
793
+
794
+ if (response.ok) {
795
+ const repo = await response.json();
796
+ remoteUrl = repo.ssh_url_to_repo || repo.http_url_to_repo;
797
+
798
+ // Add remote
799
+ await execGit(`remote add origin ${remoteUrl}`, cwd);
800
+ hasRemote = true;
801
+
802
+ console.log(` ${GREEN}✓${RESET} GitLab repository created: ${repo.web_url}`);
803
+ console.log(` ${GREEN}✓${RESET} Remote added: ${remoteUrl}`);
804
+ } else {
805
+ const error = await response.json();
806
+ if (error.message && error.message.includes('has already been taken')) {
807
+ // Repo exists, try to find and add it
808
+ console.log(` ${YELLOW}!${RESET} Repository "${dirName}" already exists on GitLab`);
809
+
810
+ // Try to get user info and construct URL
811
+ try {
812
+ const userResponse = await fetch(`${gitlab.url}/api/v4/user`, {
813
+ headers: { 'PRIVATE-TOKEN': gitlab.token },
814
+ });
815
+ if (userResponse.ok) {
816
+ const user = await userResponse.json();
817
+ remoteUrl = `git@${new URL(gitlab.url).hostname}:${user.username}/${dirName}.git`;
818
+ await execGit(`remote add origin ${remoteUrl}`, cwd);
819
+ hasRemote = true;
820
+ console.log(` ${GREEN}✓${RESET} Remote added: ${remoteUrl}`);
821
+ }
822
+ } catch (e) {
823
+ console.log(` ${DIM}Could not auto-configure remote. Add manually with:${RESET}`);
824
+ console.log(` ${DIM}git remote add origin <your-repo-url>${RESET}`);
825
+ }
826
+ } else {
827
+ console.log(` ${YELLOW}!${RESET} Failed to create repo: ${error.message || response.status}`);
828
+ }
829
+ }
830
+ } catch (e) {
831
+ console.log(` ${YELLOW}!${RESET} Failed to create GitLab repo: ${e.message}`);
832
+ }
833
+ }
834
+ }
835
+
836
+ // Step 4: Stage all changes
837
+ console.log(`\n ${DIM}Staging changes...${RESET}`);
838
+ try {
839
+ await execGit('add -A', cwd);
840
+ console.log(` ${GREEN}✓${RESET} Changes staged`);
841
+ } catch (e) {
842
+ console.log(` ${YELLOW}!${RESET} Failed to stage: ${e.message}`);
843
+ }
844
+
845
+ // Step 5: Check for changes to commit
846
+ let hasChanges = false;
847
+ try {
848
+ const status = await execGit('status --porcelain', cwd);
849
+ hasChanges = status.length > 0;
850
+ } catch (e) {
851
+ // Assume changes exist
852
+ hasChanges = true;
853
+ }
854
+
855
+ // Step 6: Commit changes
856
+ if (hasChanges) {
857
+ console.log(` ${DIM}Committing changes...${RESET}`);
858
+
859
+ // Get current task for commit message
860
+ let taskInfo = '';
861
+ try {
862
+ if (config.isAuthenticated() && config.getConnection()) {
863
+ const currentResponse = await api.getCurrentTask();
864
+ const task = currentResponse.data?.task || currentResponse.data;
865
+ if (task) {
866
+ taskInfo = task.task_key ? `[${task.task_key}] ` : `[Task #${task.id}] `;
867
+ }
868
+ }
869
+ } catch (e) {
870
+ // No task info available
871
+ }
872
+
873
+ const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19);
874
+ const commitMessage = options.message || `${taskInfo}Completed: ${timestamp}`;
875
+
876
+ try {
877
+ await execGit(`commit -m "${commitMessage.replace(/"/g, '\\"')}"`, cwd);
878
+ console.log(` ${GREEN}✓${RESET} Changes committed: "${commitMessage}"`);
879
+ } catch (e) {
880
+ if (e.message.includes('nothing to commit')) {
881
+ console.log(` ${DIM}No changes to commit${RESET}`);
882
+ } else {
883
+ console.log(` ${YELLOW}!${RESET} Commit failed: ${e.message}`);
884
+ }
885
+ }
886
+ } else {
887
+ console.log(` ${DIM}No changes to commit${RESET}`);
888
+ }
889
+
890
+ // Step 7: Push to remote
891
+ if (hasRemote) {
892
+ console.log(` ${DIM}Pushing to remote...${RESET}`);
893
+ try {
894
+ // Try to get current branch
895
+ let branch = 'main';
896
+ try {
897
+ branch = await execGit('rev-parse --abbrev-ref HEAD', cwd);
898
+ } catch (e) {
899
+ branch = 'main';
900
+ }
901
+
902
+ // Push with upstream tracking
903
+ try {
904
+ await execGit(`push -u origin ${branch}`, cwd);
905
+ console.log(` ${GREEN}✓${RESET} Pushed to origin/${branch}`);
906
+ } catch (e) {
907
+ // If push fails, try setting upstream
908
+ if (e.message.includes('no upstream branch')) {
909
+ await execGit(`push --set-upstream origin ${branch}`, cwd);
910
+ console.log(` ${GREEN}✓${RESET} Pushed to origin/${branch}`);
911
+ } else {
912
+ throw e;
913
+ }
914
+ }
915
+ } catch (e) {
916
+ console.log(` ${YELLOW}!${RESET} Push failed: ${e.message}`);
917
+ console.log(` ${DIM}You may need to push manually with: git push -u origin main${RESET}`);
918
+ }
919
+ }
920
+
921
+ // Step 8: Mark GBOS task as complete (if authenticated and connected)
922
+ if (config.isAuthenticated() && config.getConnection()) {
923
+ try {
924
+ const currentResponse = await api.getCurrentTask();
925
+ const task = currentResponse.data?.task || currentResponse.data;
926
+
927
+ if (task && task.status === 'in_progress') {
928
+ console.log(`\n ${DIM}Marking GBOS task as complete...${RESET}`);
929
+ await api.completeTask(task.id, {
930
+ completion_notes: options.message || 'Completed via gbos completed command',
931
+ });
932
+ console.log(` ${GREEN}✓${RESET} Task "${task.title || task.id}" marked as complete`);
933
+ }
934
+ } catch (e) {
935
+ // Task completion is optional, don't fail the whole command
936
+ if (e.status !== 404) {
937
+ console.log(` ${DIM}Note: Could not update GBOS task status${RESET}`);
938
+ }
939
+ }
940
+ }
941
+
942
+ // Final summary
943
+ const termWidth = getTerminalWidth();
944
+ const tableWidth = Math.min(60, termWidth - 4);
945
+
946
+ console.log(`\n${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}`);
947
+ console.log(`${GREEN}✓${RESET} ${BOLD}Completion finished!${RESET}`);
948
+ console.log(`${fg(...LOGO_PURPLE)}${'─'.repeat(tableWidth)}${RESET}\n`);
949
+
950
+ if (remoteUrl) {
951
+ console.log(` ${DIM}Repository:${RESET} ${remoteUrl}`);
952
+ }
953
+ console.log(` ${DIM}Run "gbos continue" to start the next task.${RESET}\n`);
954
+ }
955
+
700
956
  module.exports = {
701
957
  tasksCommand,
702
958
  nextTaskCommand,
703
959
  continueCommand,
704
960
  fallbackCommand,
705
961
  addTaskCommand,
962
+ completedCommand,
706
963
  generateAgentPrompt,
707
964
  };