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 +1 -1
- package/src/cli.js +9 -2
- package/src/commands/tasks.js +257 -0
package/package.json
CHANGED
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();
|
package/src/commands/tasks.js
CHANGED
|
@@ -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
|
};
|