@bretwardjames/ghp-cli 0.1.4 → 0.1.7

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 (48) hide show
  1. package/README.md +63 -11
  2. package/dist/branch-linker.d.ts +32 -21
  3. package/dist/branch-linker.d.ts.map +1 -1
  4. package/dist/branch-linker.js +94 -64
  5. package/dist/branch-linker.js.map +1 -1
  6. package/dist/commands/config.d.ts +7 -1
  7. package/dist/commands/config.d.ts.map +1 -1
  8. package/dist/commands/config.js +164 -23
  9. package/dist/commands/config.js.map +1 -1
  10. package/dist/commands/link-branch.d.ts.map +1 -1
  11. package/dist/commands/link-branch.js +7 -3
  12. package/dist/commands/link-branch.js.map +1 -1
  13. package/dist/commands/open.d.ts.map +1 -1
  14. package/dist/commands/open.js +12 -2
  15. package/dist/commands/open.js.map +1 -1
  16. package/dist/commands/plan.d.ts.map +1 -1
  17. package/dist/commands/plan.js +8 -4
  18. package/dist/commands/plan.js.map +1 -1
  19. package/dist/commands/pr.d.ts.map +1 -1
  20. package/dist/commands/pr.js +9 -6
  21. package/dist/commands/pr.js.map +1 -1
  22. package/dist/commands/start.d.ts.map +1 -1
  23. package/dist/commands/start.js +8 -3
  24. package/dist/commands/start.js.map +1 -1
  25. package/dist/commands/switch.d.ts.map +1 -1
  26. package/dist/commands/switch.js +20 -17
  27. package/dist/commands/switch.js.map +1 -1
  28. package/dist/commands/sync.js +2 -2
  29. package/dist/commands/sync.js.map +1 -1
  30. package/dist/commands/unlink-branch.d.ts.map +1 -1
  31. package/dist/commands/unlink-branch.js +14 -3
  32. package/dist/commands/unlink-branch.js.map +1 -1
  33. package/dist/commands/work.d.ts.map +1 -1
  34. package/dist/commands/work.js +28 -1
  35. package/dist/commands/work.js.map +1 -1
  36. package/dist/config.d.ts +77 -4
  37. package/dist/config.d.ts.map +1 -1
  38. package/dist/config.js +396 -20
  39. package/dist/config.js.map +1 -1
  40. package/dist/index.js +5 -3
  41. package/dist/index.js.map +1 -1
  42. package/dist/table.d.ts +1 -1
  43. package/dist/table.d.ts.map +1 -1
  44. package/dist/table.js +8 -1
  45. package/dist/table.js.map +1 -1
  46. package/dist/types.d.ts +1 -1
  47. package/dist/types.d.ts.map +1 -1
  48. package/package.json +2 -2
package/README.md CHANGED
@@ -2,10 +2,18 @@
2
2
 
3
3
  GitHub Projects CLI - manage project boards from your terminal.
4
4
 
5
+ Part of the [GHP Tools](https://github.com/bretwardjames/ghp-core) suite. Works alongside the [VS Code/Cursor extension](https://github.com/bretwardjames/vscode-gh-projects) for a complete GitHub Projects workflow.
6
+
5
7
  ## Installation
6
8
 
9
+ **Quick install (CLI + VS Code extension):**
10
+ ```bash
11
+ curl -fsSL https://raw.githubusercontent.com/bretwardjames/ghp-core/main/install.sh | bash
12
+ ```
13
+
14
+ **CLI only:**
7
15
  ```bash
8
- npm install -g ghp-cli
16
+ npm install -g @bretwardjames/ghp-cli
9
17
  ```
10
18
 
11
19
  ## Quick Start
@@ -33,6 +41,7 @@ ghp add "Fix login bug"
33
41
  ghp work
34
42
  ghp work --status "In Progress"
35
43
  ghp work --hide-done
44
+ ghp work --group priority # Group by field
36
45
 
37
46
  # Project board
38
47
  ghp plan
@@ -54,6 +63,9 @@ ghp add --list-templates # List available templates
54
63
  ghp open 123
55
64
  ghp open 123 --browser # Open in browser
56
65
 
66
+ # Edit issue description
67
+ ghp edit 123 # Opens in $EDITOR
68
+
57
69
  # Add comment
58
70
  ghp comment 123 -m "Fixed in latest commit"
59
71
  ghp comment 123 # Opens editor
@@ -87,24 +99,45 @@ ghp link-branch 123
87
99
 
88
100
  # Unlink branch
89
101
  ghp unlink-branch 123
102
+
103
+ # Sync active label with current branch
104
+ ghp sync
90
105
  ```
91
106
 
92
- ### Configuration
107
+ ## Configuration
108
+
109
+ ghp-cli uses a layered config system (like VS Code):
110
+
111
+ | Layer | Path | Purpose |
112
+ |-------|------|---------|
113
+ | **Workspace** | `.ghp/config.json` | Team settings (commit this) |
114
+ | **User** | `~/.config/ghp-cli/config.json` | Personal overrides |
115
+
116
+ Settings merge: defaults → workspace → user
117
+
118
+ ### Config Commands
93
119
 
94
120
  ```bash
95
- # View config
96
- ghp config --list
121
+ # View merged config with sources
122
+ ghp config --show
97
123
 
98
- # Edit config file
99
- ghp config --edit
124
+ # Edit user config (opens $EDITOR)
125
+ ghp config
100
126
 
101
- # Set individual values
102
- ghp config mainBranch main
103
- ```
127
+ # Edit workspace config (shared with team)
128
+ ghp config -w
104
129
 
105
- ## Configuration
130
+ # Get/set individual values
131
+ ghp config mainBranch
132
+ ghp config mainBranch develop
133
+ ghp config mainBranch develop -w # Set in workspace config
134
+
135
+ # Sync from VS Code/Cursor settings
136
+ ghp config sync
137
+ ghp config sync -w # Sync to workspace config
138
+ ```
106
139
 
107
- Config file: `~/.config/ghp-cli/config.json`
140
+ ### Config File Format
108
141
 
109
142
  ```json
110
143
  {
@@ -157,6 +190,20 @@ Filter by any field:
157
190
  --slice Size=Small # Custom project fields
158
191
  ```
159
192
 
193
+ ### Syncing with VS Code
194
+
195
+ If you use the VS Code extension, you can sync shared settings:
196
+
197
+ ```bash
198
+ ghp config sync
199
+ ```
200
+
201
+ This imports `ghProjects.*` settings from your Cursor or VS Code configuration:
202
+ - `ghProjects.mainBranch` → `mainBranch`
203
+ - `ghProjects.branchNamePattern` → `branchPattern`
204
+ - `ghProjects.startWorkingStatus` → `startWorkingStatus`
205
+ - `ghProjects.prMergedStatus` → `doneStatus`
206
+
160
207
  ## Issue Templates
161
208
 
162
209
  Place templates in `.github/ISSUE_TEMPLATE/` in your repo. When creating issues, you can:
@@ -171,6 +218,11 @@ Place templates in `.github/ISSUE_TEMPLATE/` in your repo. When creating issues,
171
218
  - GitHub account with Projects access
172
219
  - `gh` CLI recommended (for auth token)
173
220
 
221
+ ## Related
222
+
223
+ - [ghp-core](https://github.com/bretwardjames/ghp-core) - Shared library and install script
224
+ - [vscode-gh-projects](https://github.com/bretwardjames/vscode-gh-projects) - VS Code/Cursor extension
225
+
174
226
  ## License
175
227
 
176
228
  MIT
@@ -1,37 +1,48 @@
1
1
  /**
2
- * CLI-specific branch linker with file-based storage.
2
+ * CLI-specific branch linker that uses GitHub issue bodies for storage.
3
3
  *
4
- * This module wraps the core BranchLinker with a file-based StorageAdapter
5
- * that stores links in ~/.config/ghp-cli/branch-links.json
4
+ * Links are stored as hidden HTML comments in issue bodies:
5
+ * <!-- ghp-branch: feature/my-branch -->
6
+ *
7
+ * This allows links to be shared between CLI and VSCode extension.
6
8
  */
7
- import { BranchLinker, type StorageAdapter, type BranchLink } from '@bretwardjames/ghp-core';
9
+ import { type RepoInfo } from '@bretwardjames/ghp-core';
8
10
  /**
9
- * File-based storage adapter for CLI usage
11
+ * Link a branch to an issue by storing the link in the issue body.
10
12
  */
11
- declare const fileStorageAdapter: StorageAdapter;
12
- declare const linker: BranchLinker;
13
+ export declare function linkBranch(repo: RepoInfo, issueNumber: number, branch: string): Promise<boolean>;
13
14
  /**
14
- * Create a link between a branch and an issue.
15
- * Backwards-compatible function signature for existing CLI code.
15
+ * Remove the branch link from an issue.
16
+ * @returns true if a link was removed, false if no link existed
16
17
  */
17
- export declare function linkBranch(branch: string, issueNumber: number, issueTitle: string, itemId: string, repo: string): void;
18
+ export declare function unlinkBranch(repo: RepoInfo, issueNumber: number): Promise<boolean>;
18
19
  /**
19
- * Remove the link for an issue.
20
- * @returns true if a link was removed, false if no link existed
20
+ * Get the branch linked to an issue by reading the issue body.
21
21
  */
22
- export declare function unlinkBranch(repo: string, issueNumber: number): boolean;
22
+ export declare function getBranchForIssue(repo: RepoInfo, issueNumber: number): Promise<string | null>;
23
23
  /**
24
- * Get the branch linked to an issue.
24
+ * Extract issue number from a branch name.
25
+ * Supports common patterns:
26
+ * - user/123-feature-name
27
+ * - feature/123-something
28
+ * - 123-fix-bug
29
+ * - fix-123-something
25
30
  */
26
- export declare function getBranchForIssue(repo: string, issueNumber: number): string | null;
31
+ export declare function extractIssueNumberFromBranch(branchName: string): number | null;
27
32
  /**
28
- * Get the full link info for a branch.
33
+ * Result of finding an issue for a branch.
29
34
  */
30
- export declare function getIssueForBranch(repo: string, branch: string): BranchLink | null;
35
+ export interface BranchIssueLink {
36
+ issueNumber: number;
37
+ issueTitle: string;
38
+ branch: string;
39
+ }
31
40
  /**
32
- * Get all links for a repository.
41
+ * Find the issue linked to a branch.
42
+ * This first extracts the issue number from the branch name pattern,
43
+ * then verifies the link exists in the issue body.
44
+ *
45
+ * @returns Issue info if found and verified, null otherwise
33
46
  */
34
- export declare function getAllLinksForRepo(repo: string): BranchLink[];
35
- export type { BranchLink } from '@bretwardjames/ghp-core';
36
- export { linker, fileStorageAdapter };
47
+ export declare function getIssueForBranch(repo: RepoInfo, branchName: string): Promise<BranchIssueLink | null>;
37
48
  //# sourceMappingURL=branch-linker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"branch-linker.d.ts","sourceRoot":"","sources":["../src/branch-linker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EACH,YAAY,EACZ,KAAK,cAAc,EACnB,KAAK,UAAU,EAClB,MAAM,yBAAyB,CAAC;AAKjC;;GAEG;AACH,QAAA,MAAM,kBAAkB,EAAE,cAmBzB,CAAC;AAGF,QAAA,MAAM,MAAM,cAAuC,CAAC;AAEpD;;;GAGG;AACH,wBAAgB,UAAU,CACtB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACb,IAAI,CAIN;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAcvE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIlF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAGjF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CAG7D;AAGD,YAAY,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAG1D,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC"}
1
+ {"version":3,"file":"branch-linker.d.ts","sourceRoot":"","sources":["../src/branch-linker.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAIH,KAAK,QAAQ,EAChB,MAAM,yBAAyB,CAAC;AAGjC;;GAEG;AACH,wBAAsB,UAAU,CAC5B,IAAI,EAAE,QAAQ,EACd,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAC9B,IAAI,EAAE,QAAQ,EACd,WAAW,EAAE,MAAM,GACpB,OAAO,CAAC,OAAO,CAAC,CAgBlB;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACnC,IAAI,EAAE,QAAQ,EACd,WAAW,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQxB;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgB9E;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACnC,IAAI,EAAE,QAAQ,EACd,UAAU,EAAE,MAAM,GACnB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CA4BjC"}
@@ -1,86 +1,116 @@
1
1
  /**
2
- * CLI-specific branch linker with file-based storage.
2
+ * CLI-specific branch linker that uses GitHub issue bodies for storage.
3
3
  *
4
- * This module wraps the core BranchLinker with a file-based StorageAdapter
5
- * that stores links in ~/.config/ghp-cli/branch-links.json
6
- */
7
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
8
- import { homedir } from 'os';
9
- import { join } from 'path';
10
- import { BranchLinker, } from '@bretwardjames/ghp-core';
11
- const DATA_DIR = join(homedir(), '.config', 'ghp-cli');
12
- const LINKS_FILE = join(DATA_DIR, 'branch-links.json');
13
- /**
14
- * File-based storage adapter for CLI usage
4
+ * Links are stored as hidden HTML comments in issue bodies:
5
+ * <!-- ghp-branch: feature/my-branch -->
6
+ *
7
+ * This allows links to be shared between CLI and VSCode extension.
15
8
  */
16
- const fileStorageAdapter = {
17
- load() {
18
- try {
19
- if (existsSync(LINKS_FILE)) {
20
- const data = readFileSync(LINKS_FILE, 'utf-8');
21
- return JSON.parse(data);
22
- }
23
- }
24
- catch {
25
- // Ignore errors, return empty array
26
- }
27
- return [];
28
- },
29
- save(links) {
30
- if (!existsSync(DATA_DIR)) {
31
- mkdirSync(DATA_DIR, { recursive: true });
32
- }
33
- writeFileSync(LINKS_FILE, JSON.stringify(links, null, 2));
34
- },
35
- };
36
- // Create singleton linker instance
37
- const linker = new BranchLinker(fileStorageAdapter);
9
+ import { parseBranchLink, setBranchLinkInBody, removeBranchLinkFromBody, } from '@bretwardjames/ghp-core';
10
+ import { api } from './github-api.js';
38
11
  /**
39
- * Create a link between a branch and an issue.
40
- * Backwards-compatible function signature for existing CLI code.
12
+ * Link a branch to an issue by storing the link in the issue body.
41
13
  */
42
- export function linkBranch(branch, issueNumber, issueTitle, itemId, repo) {
43
- // Use sync-like behavior for backwards compatibility
44
- // The file adapter is synchronous so this works
45
- linker.link(branch, issueNumber, issueTitle, itemId, repo);
14
+ export async function linkBranch(repo, issueNumber, branch) {
15
+ try {
16
+ const details = await api.getIssueDetails(repo, issueNumber);
17
+ const currentBody = details?.body ?? '';
18
+ const newBody = setBranchLinkInBody(currentBody, branch);
19
+ return await api.updateIssueBody(repo, issueNumber, newBody);
20
+ }
21
+ catch (error) {
22
+ console.error('Failed to link branch:', error);
23
+ return false;
24
+ }
46
25
  }
47
26
  /**
48
- * Remove the link for an issue.
27
+ * Remove the branch link from an issue.
49
28
  * @returns true if a link was removed, false if no link existed
50
29
  */
51
- export function unlinkBranch(repo, issueNumber) {
52
- // Load, filter, and save synchronously for backwards compatibility
53
- const adapter = fileStorageAdapter;
54
- const links = adapter.load();
55
- const filtered = links.filter(l => !(l.repo === repo && l.issueNumber === issueNumber));
56
- if (filtered.length === links.length) {
30
+ export async function unlinkBranch(repo, issueNumber) {
31
+ try {
32
+ const details = await api.getIssueDetails(repo, issueNumber);
33
+ const currentBody = details?.body ?? '';
34
+ // Check if there's a link to remove
35
+ if (!parseBranchLink(currentBody)) {
36
+ return false;
37
+ }
38
+ const newBody = removeBranchLinkFromBody(currentBody);
39
+ return await api.updateIssueBody(repo, issueNumber, newBody);
40
+ }
41
+ catch (error) {
42
+ console.error('Failed to unlink branch:', error);
57
43
  return false;
58
44
  }
59
- adapter.save(filtered);
60
- return true;
61
45
  }
62
46
  /**
63
- * Get the branch linked to an issue.
47
+ * Get the branch linked to an issue by reading the issue body.
64
48
  */
65
- export function getBranchForIssue(repo, issueNumber) {
66
- const links = fileStorageAdapter.load();
67
- const link = links.find(l => l.repo === repo && l.issueNumber === issueNumber);
68
- return link?.branch || null;
49
+ export async function getBranchForIssue(repo, issueNumber) {
50
+ try {
51
+ const details = await api.getIssueDetails(repo, issueNumber);
52
+ return parseBranchLink(details?.body);
53
+ }
54
+ catch (error) {
55
+ console.error('Failed to get branch for issue:', error);
56
+ return null;
57
+ }
69
58
  }
70
59
  /**
71
- * Get the full link info for a branch.
60
+ * Extract issue number from a branch name.
61
+ * Supports common patterns:
62
+ * - user/123-feature-name
63
+ * - feature/123-something
64
+ * - 123-fix-bug
65
+ * - fix-123-something
72
66
  */
73
- export function getIssueForBranch(repo, branch) {
74
- const links = fileStorageAdapter.load();
75
- return links.find(l => l.repo === repo && l.branch === branch) || null;
67
+ export function extractIssueNumberFromBranch(branchName) {
68
+ const patterns = [
69
+ /\/(\d+)-/, // user/123-title
70
+ /^(\d+)-/, // 123-title
71
+ /-(\d+)-/, // feature-123-title
72
+ /[/#](\d+)$/, // ends with #123 or /123
73
+ ];
74
+ for (const pattern of patterns) {
75
+ const match = branchName.match(pattern);
76
+ if (match) {
77
+ return parseInt(match[1], 10);
78
+ }
79
+ }
80
+ return null;
76
81
  }
77
82
  /**
78
- * Get all links for a repository.
83
+ * Find the issue linked to a branch.
84
+ * This first extracts the issue number from the branch name pattern,
85
+ * then verifies the link exists in the issue body.
86
+ *
87
+ * @returns Issue info if found and verified, null otherwise
79
88
  */
80
- export function getAllLinksForRepo(repo) {
81
- const links = fileStorageAdapter.load();
82
- return links.filter(l => l.repo === repo);
89
+ export async function getIssueForBranch(repo, branchName) {
90
+ // First, try to extract issue number from branch name
91
+ const issueNumber = extractIssueNumberFromBranch(branchName);
92
+ if (!issueNumber) {
93
+ return null;
94
+ }
95
+ try {
96
+ // Get issue details to verify and get title
97
+ const details = await api.getIssueDetails(repo, issueNumber);
98
+ if (!details) {
99
+ return null;
100
+ }
101
+ // Check if this issue has a link to this branch
102
+ const linkedBranch = parseBranchLink(details.body);
103
+ // If not explicitly linked, still return info based on branch naming convention
104
+ // This allows workflows that rely on branch naming patterns to work
105
+ return {
106
+ issueNumber,
107
+ issueTitle: details.title,
108
+ branch: linkedBranch === branchName ? branchName : branchName,
109
+ };
110
+ }
111
+ catch (error) {
112
+ console.error('Failed to verify issue for branch:', error);
113
+ return null;
114
+ }
83
115
  }
84
- // Also export the linker instance and adapter for advanced usage
85
- export { linker, fileStorageAdapter };
86
116
  //# sourceMappingURL=branch-linker.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"branch-linker.js","sourceRoot":"","sources":["../src/branch-linker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EACH,YAAY,GAGf,MAAM,yBAAyB,CAAC;AAEjC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;AACvD,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC;AAEvD;;GAEG;AACH,MAAM,kBAAkB,GAAmB;IACvC,IAAI;QACA,IAAI,CAAC;YACD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBACzB,MAAM,IAAI,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;gBAC/C,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACL,oCAAoC;QACxC,CAAC;QACD,OAAO,EAAE,CAAC;IACd,CAAC;IAED,IAAI,CAAC,KAAmB;QACpB,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxB,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,CAAC;QACD,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9D,CAAC;CACJ,CAAC;AAEF,mCAAmC;AACnC,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,kBAAkB,CAAC,CAAC;AAEpD;;;GAGG;AACH,MAAM,UAAU,UAAU,CACtB,MAAc,EACd,WAAmB,EACnB,UAAkB,EAClB,MAAc,EACd,IAAY;IAEZ,qDAAqD;IACrD,gDAAgD;IAChD,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;AAC/D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,WAAmB;IAC1D,mEAAmE;IACnE,MAAM,OAAO,GAAG,kBAAkB,CAAC;IACnC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAkB,CAAC;IAC7C,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAC9B,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,WAAW,KAAK,WAAW,CAAC,CACtD,CAAC;IAEF,IAAI,QAAQ,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;QACnC,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACvB,OAAO,IAAI,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,WAAmB;IAC/D,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,EAAkB,CAAC;IACxD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,WAAW,KAAK,WAAW,CAAC,CAAC;IAC/E,OAAO,IAAI,EAAE,MAAM,IAAI,IAAI,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,MAAc;IAC1D,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,EAAkB,CAAC;IACxD,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC;AAC3E,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC3C,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,EAAkB,CAAC;IACxD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AAC9C,CAAC;AAKD,iEAAiE;AACjE,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC"}
1
+ {"version":3,"file":"branch-linker.js","sourceRoot":"","sources":["../src/branch-linker.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,11 @@
1
+ export declare function configSyncCommand(options?: {
2
+ workspace?: boolean;
3
+ user?: boolean;
4
+ }): Promise<void>;
1
5
  export declare function configCommand(key?: string, value?: string, options?: {
2
- list?: boolean;
6
+ show?: boolean;
3
7
  edit?: boolean;
8
+ workspace?: boolean;
9
+ user?: boolean;
4
10
  }): Promise<void>;
5
11
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAyDA,wBAAsB,aAAa,CAC/B,GAAG,CAAC,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GACjD,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AA4FA,wBAAsB,iBAAiB,CACnC,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GACtD,OAAO,CAAC,IAAI,CAAC,CAiDf;AAED,wBAAsB,aAAa,CAC/B,GAAG,CAAC,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GACtF,OAAO,CAAC,IAAI,CAAC,CA+Gf"}
@@ -1,10 +1,47 @@
1
1
  import { existsSync, writeFileSync, mkdirSync } from 'fs';
2
2
  import { spawn } from 'child_process';
3
3
  import { dirname } from 'path';
4
- import { getConfig, setConfig, listConfig, CONFIG_KEYS, getConfigPath } from '../config.js';
4
+ import chalk from 'chalk';
5
+ import { getConfig, setConfig, getFullConfigWithSources, CONFIG_KEYS, getConfigPath, getWorkspaceConfigPath, getUserConfigPath, syncFromVSCode, getVSCodeSettingsPaths } from '../config.js';
6
+ const SOURCE_LABELS = {
7
+ 'default': chalk.dim('(default)'),
8
+ 'workspace': chalk.cyan('(workspace)'),
9
+ 'user': chalk.green('(user)'),
10
+ };
5
11
  function isValidKey(key) {
6
12
  return CONFIG_KEYS.includes(key);
7
13
  }
14
+ function resolveScope(options) {
15
+ if (options.workspace)
16
+ return 'workspace';
17
+ return 'user';
18
+ }
19
+ function formatShortcut(shortcut, indent = '') {
20
+ if (shortcut.status) {
21
+ const statusVal = Array.isArray(shortcut.status)
22
+ ? shortcut.status.join(', ')
23
+ : shortcut.status;
24
+ console.log(`${indent}status: ${statusVal}`);
25
+ }
26
+ if (shortcut.mine)
27
+ console.log(`${indent}mine: true`);
28
+ if (shortcut.unassigned)
29
+ console.log(`${indent}unassigned: true`);
30
+ if (shortcut.project)
31
+ console.log(`${indent}project: ${shortcut.project}`);
32
+ if (shortcut.sort)
33
+ console.log(`${indent}sort: ${shortcut.sort}`);
34
+ if (shortcut.slice && shortcut.slice.length > 0) {
35
+ console.log(`${indent}slice: ${shortcut.slice.join(', ')}`);
36
+ }
37
+ // Show other properties that might exist (like list, all, group)
38
+ const knownKeys = ['status', 'mine', 'unassigned', 'project', 'sort', 'slice'];
39
+ for (const [key, value] of Object.entries(shortcut)) {
40
+ if (!knownKeys.includes(key) && value !== undefined) {
41
+ console.log(`${indent}${key}: ${value}`);
42
+ }
43
+ }
44
+ }
8
45
  const CONFIG_TEMPLATE = `{
9
46
  "_comment": "ghp-cli configuration - see https://github.com/your/ghp-cli for docs",
10
47
 
@@ -49,34 +86,119 @@ function openInEditor(filePath) {
49
86
  console.log(`Config file is at: ${filePath}`);
50
87
  });
51
88
  }
52
- export async function configCommand(key, value, options = {}) {
53
- if (options.edit) {
54
- const configPath = getConfigPath();
55
- // Create config with template if it doesn't exist
56
- if (!existsSync(configPath)) {
57
- mkdirSync(dirname(configPath), { recursive: true });
58
- writeFileSync(configPath, CONFIG_TEMPLATE);
59
- console.log(`Created config file: ${configPath}`);
89
+ export async function configSyncCommand(options = {}) {
90
+ const scope = resolveScope(options);
91
+ console.log(chalk.bold('Syncing from VS Code/Cursor settings...'));
92
+ console.log();
93
+ const paths = getVSCodeSettingsPaths();
94
+ console.log(chalk.dim('Looking for settings in:'));
95
+ console.log(chalk.dim(` Workspace: ${paths.workspace || '(not in git repo)'}`));
96
+ console.log(chalk.dim(` Cursor: ${paths.cursorUser}`));
97
+ console.log(chalk.dim(` VS Code: ${paths.codeUser}`));
98
+ console.log();
99
+ const result = syncFromVSCode(scope);
100
+ // Report any parse errors
101
+ if (result.errors.length > 0) {
102
+ for (const error of result.errors) {
103
+ console.log(chalk.yellow(`Warning: ${error}`));
60
104
  }
61
- openInEditor(configPath);
62
- return;
105
+ console.log();
63
106
  }
64
- if (options.list || (!key && !value)) {
65
- const config = listConfig();
66
- console.log('\nConfiguration:');
67
- console.log('─'.repeat(40));
68
- for (const [k, v] of Object.entries(config)) {
69
- console.log(` ${k}: ${v || '(not set)'}`);
107
+ if (result.synced.length === 0) {
108
+ if (result.skipped.length > 0) {
109
+ console.log(chalk.yellow('No syncable settings found.'));
110
+ console.log(chalk.dim(`Found ${result.skipped.length} extension-only setting(s): ${result.skipped.join(', ')}`));
111
+ console.log();
112
+ console.log(chalk.dim('Syncable settings: mainBranch, branchNamePattern, startWorkingStatus, prMergedStatus'));
70
113
  }
71
- console.log('\nAvailable keys:', CONFIG_KEYS.join(', '));
114
+ else {
115
+ console.log(chalk.yellow('No ghProjects.* settings found to sync.'));
116
+ console.log(chalk.dim('Make sure you have settings like ghProjects.mainBranch in your editor.'));
117
+ }
118
+ return;
119
+ }
120
+ console.log(chalk.green(`Synced ${result.synced.length} setting(s) from ${result.editor}:`));
121
+ for (const { key, value, source } of result.synced) {
122
+ const sourceLabel = source === 'workspace' ? chalk.cyan('(workspace)') : chalk.green('(user)');
123
+ console.log(` ${key}: ${value} ${sourceLabel}`);
124
+ }
125
+ if (result.skipped.length > 0) {
126
+ console.log();
127
+ console.log(chalk.dim(`Skipped ${result.skipped.length} extension-only setting(s): ${result.skipped.join(', ')}`));
128
+ }
129
+ console.log();
130
+ console.log(chalk.dim(`Saved to ${scope} config: ${getConfigPath(scope)}`));
131
+ }
132
+ export async function configCommand(key, value, options = {}) {
133
+ const scope = resolveScope(options);
134
+ // Handle 'sync' as first argument
135
+ if (key === 'sync') {
136
+ await configSyncCommand(options);
72
137
  return;
73
138
  }
74
- if (key && !isValidKey(key)) {
75
- console.log(`Unknown config key: "${key}"`);
76
- console.log('Available keys:', CONFIG_KEYS.join(', '));
139
+ // --show: display merged config from all sources with source indicators
140
+ if (options.show) {
141
+ const fullConfig = getFullConfigWithSources();
142
+ console.log('\n' + chalk.bold('Settings:'));
143
+ console.log('─'.repeat(60));
144
+ for (const [key, { value, source }] of Object.entries(fullConfig.settings)) {
145
+ const sourceLabel = SOURCE_LABELS[source];
146
+ console.log(` ${key}: ${value || chalk.dim('(not set)')} ${sourceLabel}`);
147
+ }
148
+ console.log('\n' + chalk.bold('Defaults:'));
149
+ console.log('─'.repeat(60));
150
+ // Plan defaults
151
+ const planDefaults = fullConfig.defaults.plan;
152
+ const planSourceLabel = SOURCE_LABELS[planDefaults.source];
153
+ if (Object.keys(planDefaults.value).length > 0) {
154
+ console.log(` ${chalk.cyan('plan')} ${planSourceLabel}`);
155
+ formatShortcut(planDefaults.value, ' ');
156
+ }
157
+ else {
158
+ console.log(` ${chalk.cyan('plan')}: ${chalk.dim('(none)')} ${planSourceLabel}`);
159
+ }
160
+ // AddIssue defaults
161
+ const addIssueDefaults = fullConfig.defaults.addIssue;
162
+ const addIssueSourceLabel = SOURCE_LABELS[addIssueDefaults.source];
163
+ if (Object.keys(addIssueDefaults.value).length > 0) {
164
+ console.log(` ${chalk.cyan('addIssue')} ${addIssueSourceLabel}`);
165
+ for (const [k, v] of Object.entries(addIssueDefaults.value)) {
166
+ if (v)
167
+ console.log(` ${k}: ${v}`);
168
+ }
169
+ }
170
+ else {
171
+ console.log(` ${chalk.cyan('addIssue')}: ${chalk.dim('(none)')} ${addIssueSourceLabel}`);
172
+ }
173
+ console.log('\n' + chalk.bold('Shortcuts:'));
174
+ console.log('─'.repeat(60));
175
+ const shortcuts = fullConfig.shortcuts;
176
+ if (Object.keys(shortcuts).length > 0) {
177
+ for (const [name, { value, source }] of Object.entries(shortcuts)) {
178
+ const sourceLabel = SOURCE_LABELS[source];
179
+ console.log(` ${chalk.cyan(name)} ${sourceLabel}`);
180
+ formatShortcut(value, ' ');
181
+ }
182
+ }
183
+ else {
184
+ console.log(` ${chalk.dim('(none)')}`);
185
+ }
186
+ console.log('\n' + chalk.bold('Config files:'));
187
+ console.log('─'.repeat(60));
188
+ console.log(` User: ${getUserConfigPath()}`);
189
+ const workspacePath = getWorkspaceConfigPath();
190
+ console.log(` Workspace: ${workspacePath || '(not in a git repository)'}`);
191
+ console.log('\nUse "ghp config" to edit user config');
192
+ console.log('Use "ghp config -w" to edit workspace config (shared with team)');
77
193
  return;
78
194
  }
195
+ // Get/set specific key
79
196
  if (key && !value) {
197
+ if (!isValidKey(key)) {
198
+ console.log(`Unknown config key: "${key}"`);
199
+ console.log('Available keys:', CONFIG_KEYS.join(', '));
200
+ return;
201
+ }
80
202
  const val = getConfig(key);
81
203
  if (val !== undefined) {
82
204
  console.log(val);
@@ -87,8 +209,27 @@ export async function configCommand(key, value, options = {}) {
87
209
  return;
88
210
  }
89
211
  if (key && value) {
90
- setConfig(key, value);
91
- console.log(`Set ${key} = ${value}`);
212
+ if (!isValidKey(key)) {
213
+ console.log(`Unknown config key: "${key}"`);
214
+ console.log('Available keys:', CONFIG_KEYS.join(', '));
215
+ return;
216
+ }
217
+ setConfig(key, value, scope);
218
+ console.log(`Set ${key} = ${value} (in ${scope} config)`);
219
+ return;
220
+ }
221
+ // Default: open editor (when no key/value provided)
222
+ const configPath = getConfigPath(scope);
223
+ if (scope === 'workspace' && configPath === '(not in a git repository)') {
224
+ console.error('Error: Not in a git repository. Cannot edit workspace config.');
225
+ return;
226
+ }
227
+ // Create config with template if it doesn't exist
228
+ if (!existsSync(configPath)) {
229
+ mkdirSync(dirname(configPath), { recursive: true });
230
+ writeFileSync(configPath, CONFIG_TEMPLATE);
231
+ console.log(`Created config file: ${configPath}`);
92
232
  }
233
+ openInEditor(configPath);
93
234
  }
94
235
  //# sourceMappingURL=config.js.map