@fiftth/fiftth-cli 1.0.1 → 1.1.1

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 (116) hide show
  1. package/.fiftthnexus/.github/workflows/copilot-orchestrator.yml +78 -0
  2. package/.fiftthnexus/actions/Dockerfile +34 -0
  3. package/.fiftthnexus/actions/copilot-agent.mjs +269 -0
  4. package/.fiftthnexus/actions/package.json +8 -0
  5. package/.fiftthnexus/orchestrator.ts +2304 -0
  6. package/.fiftthnexus/skills/env-implement-prompt.md +65 -0
  7. package/.fiftthnexus/skills/env-plan-prompt.md +33 -0
  8. package/.fiftthnexus/skills/env-review-prompt.md +61 -0
  9. package/.fiftthnexus/skills/grill-me.md +9 -0
  10. package/.fiftthnexus/skills/prd-to-issues.md +150 -0
  11. package/.fiftthnexus/skills/write-prd.md +70 -0
  12. package/README.md +216 -25
  13. package/dist/api/client.d.ts +6 -0
  14. package/dist/api/client.d.ts.map +1 -1
  15. package/dist/api/client.js +13 -2
  16. package/dist/api/client.js.map +1 -1
  17. package/dist/commands/checkout.d.ts +6 -1
  18. package/dist/commands/checkout.d.ts.map +1 -1
  19. package/dist/commands/checkout.js +415 -44
  20. package/dist/commands/checkout.js.map +1 -1
  21. package/dist/commands/login.d.ts +0 -2
  22. package/dist/commands/login.d.ts.map +1 -1
  23. package/dist/commands/login.js +83 -32
  24. package/dist/commands/login.js.map +1 -1
  25. package/dist/commands/model.d.ts +2 -0
  26. package/dist/commands/model.d.ts.map +1 -0
  27. package/dist/commands/model.js +32 -0
  28. package/dist/commands/model.js.map +1 -0
  29. package/dist/commands/planningContext.d.ts +6 -0
  30. package/dist/commands/planningContext.d.ts.map +1 -0
  31. package/dist/commands/planningContext.js +91 -0
  32. package/dist/commands/planningContext.js.map +1 -0
  33. package/dist/commands/repo.d.ts.map +1 -1
  34. package/dist/commands/repo.js +38 -15
  35. package/dist/commands/repo.js.map +1 -1
  36. package/dist/commands/skills.d.ts +2 -0
  37. package/dist/commands/skills.d.ts.map +1 -0
  38. package/dist/commands/skills.js +123 -0
  39. package/dist/commands/skills.js.map +1 -0
  40. package/dist/commands/use.d.ts +1 -5
  41. package/dist/commands/use.d.ts.map +1 -1
  42. package/dist/commands/use.js +63 -48
  43. package/dist/commands/use.js.map +1 -1
  44. package/dist/index.js +86 -27
  45. package/dist/index.js.map +1 -1
  46. package/dist/services/nexusService.d.ts +30 -0
  47. package/dist/services/nexusService.d.ts.map +1 -0
  48. package/dist/services/nexusService.js +188 -0
  49. package/dist/services/nexusService.js.map +1 -0
  50. package/dist/services/prdService.d.ts +12 -0
  51. package/dist/services/prdService.d.ts.map +1 -0
  52. package/dist/services/prdService.js +103 -0
  53. package/dist/services/prdService.js.map +1 -0
  54. package/dist/services/taskSelection.d.ts +10 -0
  55. package/dist/services/taskSelection.d.ts.map +1 -0
  56. package/dist/services/taskSelection.js +112 -0
  57. package/dist/services/taskSelection.js.map +1 -0
  58. package/dist/services/taskService.d.ts +23 -1
  59. package/dist/services/taskService.d.ts.map +1 -1
  60. package/dist/services/taskService.js +118 -12
  61. package/dist/services/taskService.js.map +1 -1
  62. package/dist/utils/config.d.ts +2 -0
  63. package/dist/utils/config.d.ts.map +1 -1
  64. package/dist/utils/config.js +20 -3
  65. package/dist/utils/config.js.map +1 -1
  66. package/dist/utils/dashboard.d.ts +65 -0
  67. package/dist/utils/dashboard.d.ts.map +1 -0
  68. package/dist/utils/dashboard.js +205 -0
  69. package/dist/utils/dashboard.js.map +1 -0
  70. package/dist/utils/models.d.ts +14 -0
  71. package/dist/utils/models.d.ts.map +1 -0
  72. package/dist/utils/models.js +89 -0
  73. package/dist/utils/models.js.map +1 -0
  74. package/dist/utils/ui.d.ts +6 -0
  75. package/dist/utils/ui.d.ts.map +1 -1
  76. package/dist/utils/ui.js +22 -1
  77. package/dist/utils/ui.js.map +1 -1
  78. package/dist/utils/version.d.ts +4 -0
  79. package/dist/utils/version.d.ts.map +1 -0
  80. package/dist/utils/version.js +26 -0
  81. package/dist/utils/version.js.map +1 -0
  82. package/package.json +9 -4
  83. package/.github/workflows/publish-npm.yml +0 -62
  84. package/dist/commands/tasks.d.ts +0 -2
  85. package/dist/commands/tasks.d.ts.map +0 -1
  86. package/dist/commands/tasks.js +0 -69
  87. package/dist/commands/tasks.js.map +0 -1
  88. package/dist/context/runtimeContext.d.ts +0 -14
  89. package/dist/context/runtimeContext.d.ts.map +0 -1
  90. package/dist/context/runtimeContext.js +0 -21
  91. package/dist/context/runtimeContext.js.map +0 -1
  92. package/dist/services/taskContext.d.ts +0 -14
  93. package/dist/services/taskContext.d.ts.map +0 -1
  94. package/dist/services/taskContext.js +0 -15
  95. package/dist/services/taskContext.js.map +0 -1
  96. package/dist/utils/api.d.ts +0 -10
  97. package/dist/utils/api.d.ts.map +0 -1
  98. package/dist/utils/api.js +0 -25
  99. package/dist/utils/api.js.map +0 -1
  100. package/src/api/client.ts +0 -31
  101. package/src/commands/checkout.ts +0 -101
  102. package/src/commands/login.ts +0 -145
  103. package/src/commands/repo.ts +0 -113
  104. package/src/commands/tasks.ts +0 -86
  105. package/src/commands/use.ts +0 -149
  106. package/src/config/configService.ts +0 -56
  107. package/src/context/runtimeContext.ts +0 -42
  108. package/src/git/gitService.ts +0 -29
  109. package/src/index.ts +0 -133
  110. package/src/services/taskContext.ts +0 -32
  111. package/src/services/taskService.ts +0 -53
  112. package/src/utils/api.ts +0 -41
  113. package/src/utils/config.ts +0 -48
  114. package/src/utils/ui.ts +0 -46
  115. package/tsconfig.json +0 -18
  116. package/vitest.config.ts +0 -8
@@ -1,14 +0,0 @@
1
- interface SelectedTaskRepository {
2
- fullName: string;
3
- branch: string;
4
- }
5
- export interface SelectedTaskContext {
6
- taskId: string;
7
- title: string;
8
- repositories: SelectedTaskRepository[];
9
- }
10
- export declare function setRuntimeSelectedTask(task: SelectedTaskContext): void;
11
- export declare function getRuntimeSelectedTask(): SelectedTaskContext | null;
12
- export declare function clearRuntimeSelectedTask(): void;
13
- export {};
14
- //# sourceMappingURL=runtimeContext.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runtimeContext.d.ts","sourceRoot":"","sources":["../../src/context/runtimeContext.ts"],"names":[],"mappings":"AAEA,UAAU,sBAAsB;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,sBAAsB,EAAE,CAAA;CACvC;AAaD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAGtE;AAED,wBAAgB,sBAAsB,IAAI,mBAAmB,GAAG,IAAI,CAMnE;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAG/C"}
@@ -1,21 +0,0 @@
1
- import Conf from 'conf';
2
- const contextStore = new Conf({
3
- projectName: 'fiftth-cli',
4
- defaults: {},
5
- });
6
- let selectedTaskInMemory = null;
7
- export function setRuntimeSelectedTask(task) {
8
- selectedTaskInMemory = task;
9
- contextStore.set('selectedTask', task);
10
- }
11
- export function getRuntimeSelectedTask() {
12
- if (selectedTaskInMemory) {
13
- return selectedTaskInMemory;
14
- }
15
- return contextStore.get('selectedTask') ?? null;
16
- }
17
- export function clearRuntimeSelectedTask() {
18
- selectedTaskInMemory = null;
19
- contextStore.delete('selectedTask');
20
- }
21
- //# sourceMappingURL=runtimeContext.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runtimeContext.js","sourceRoot":"","sources":["../../src/context/runtimeContext.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AAiBvB,MAAM,YAAY,GAAG,IAAI,IAAI,CAAsB;IACjD,WAAW,EAAE,YAAY;IACzB,QAAQ,EAAE,EAAE;CACb,CAAC,CAAA;AAEF,IAAI,oBAAoB,GAA+B,IAAI,CAAA;AAE3D,MAAM,UAAU,sBAAsB,CAAC,IAAyB;IAC9D,oBAAoB,GAAG,IAAI,CAAA;IAC3B,YAAY,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,CAAA;AACxC,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,IAAI,oBAAoB,EAAE,CAAC;QACzB,OAAO,oBAAoB,CAAA;IAC7B,CAAC;IAED,OAAO,YAAY,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,IAAI,CAAA;AACjD,CAAC;AAED,MAAM,UAAU,wBAAwB;IACtC,oBAAoB,GAAG,IAAI,CAAA;IAC3B,YAAY,CAAC,MAAM,CAAC,cAAc,CAAC,CAAA;AACrC,CAAC"}
@@ -1,14 +0,0 @@
1
- export interface SelectedTaskRepository {
2
- repoName: string;
3
- branch: string;
4
- }
5
- export interface SelectedTaskContext {
6
- taskId: string;
7
- title: string;
8
- repositories: SelectedTaskRepository[];
9
- }
10
- export declare function setSelectedTaskContext(task: SelectedTaskContext): void;
11
- export declare function getSelectedTaskContext(): SelectedTaskContext | null;
12
- export declare function getSelectedTaskBranch(repoName: string): string | undefined;
13
- export declare function clearSelectedTaskContext(): void;
14
- //# sourceMappingURL=taskContext.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"taskContext.d.ts","sourceRoot":"","sources":["../../src/services/taskContext.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,sBAAsB,EAAE,CAAA;CACvC;AAID,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAEtE;AAED,wBAAgB,sBAAsB,IAAI,mBAAmB,GAAG,IAAI,CAEnE;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAM1E;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C"}
@@ -1,15 +0,0 @@
1
- let selectedTaskContext = null;
2
- export function setSelectedTaskContext(task) {
3
- selectedTaskContext = task;
4
- }
5
- export function getSelectedTaskContext() {
6
- return selectedTaskContext;
7
- }
8
- export function getSelectedTaskBranch(repoName) {
9
- const repository = selectedTaskContext?.repositories.find((entry) => entry.repoName === repoName);
10
- return repository?.branch;
11
- }
12
- export function clearSelectedTaskContext() {
13
- selectedTaskContext = null;
14
- }
15
- //# sourceMappingURL=taskContext.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"taskContext.js","sourceRoot":"","sources":["../../src/services/taskContext.ts"],"names":[],"mappings":"AAWA,IAAI,mBAAmB,GAA+B,IAAI,CAAA;AAE1D,MAAM,UAAU,sBAAsB,CAAC,IAAyB;IAC9D,mBAAmB,GAAG,IAAI,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,sBAAsB;IACpC,OAAO,mBAAmB,CAAA;AAC5B,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,QAAgB;IACpD,MAAM,UAAU,GAAG,mBAAmB,EAAE,YAAY,CAAC,IAAI,CACvD,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,QAAQ,CACvC,CAAA;IAED,OAAO,UAAU,EAAE,MAAM,CAAA;AAC3B,CAAC;AAED,MAAM,UAAU,wBAAwB;IACtC,mBAAmB,GAAG,IAAI,CAAA;AAC5B,CAAC"}
@@ -1,10 +0,0 @@
1
- export interface Workspace {
2
- id: string;
3
- name: string;
4
- slug: string;
5
- }
6
- export interface ApiClient {
7
- getWorkspaces(): Promise<Workspace[]>;
8
- }
9
- export declare function createApiClient(): ApiClient;
10
- //# sourceMappingURL=api.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/utils/api.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,SAAS;IACxB,aAAa,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC,CAAA;CACtC;AAED,wBAAgB,eAAe,IAAI,SAAS,CA4B3C"}
package/dist/utils/api.js DELETED
@@ -1,25 +0,0 @@
1
- import { loadConfig } from './config.js';
2
- export function createApiClient() {
3
- const config = loadConfig();
4
- async function request(endpoint) {
5
- const url = `${config.host}${endpoint}`;
6
- const response = await fetch(url, {
7
- headers: {
8
- Authorization: `Bearer ${config.token}`,
9
- 'Content-Type': 'application/json',
10
- 'User-Agent': 'fiftth-cli/0.1.0',
11
- },
12
- });
13
- if (!response.ok) {
14
- const body = await response.text().catch(() => '');
15
- throw new Error(`API request failed: ${response.status} ${response.statusText}${body ? ` — ${body}` : ''}`);
16
- }
17
- return response.json();
18
- }
19
- return {
20
- getWorkspaces() {
21
- return request('/workspaces');
22
- },
23
- };
24
- }
25
- //# sourceMappingURL=api.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/utils/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAYxC,MAAM,UAAU,eAAe;IAC7B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAA;IAE3B,KAAK,UAAU,OAAO,CAAI,QAAgB;QACxC,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,IAAI,GAAG,QAAQ,EAAE,CAAA;QACvC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,MAAM,CAAC,KAAK,EAAE;gBACvC,cAAc,EAAE,kBAAkB;gBAClC,YAAY,EAAE,kBAAkB;aACjC;SACF,CAAC,CAAA;QAEF,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;YAClD,MAAM,IAAI,KAAK,CACb,uBAAuB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAC3F,CAAA;QACH,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,EAAgB,CAAA;IACtC,CAAC;IAED,OAAO;QACL,aAAa;YACX,OAAO,OAAO,CAAc,aAAa,CAAC,CAAA;QAC5C,CAAC;KACF,CAAA;AACH,CAAC"}
package/src/api/client.ts DELETED
@@ -1,31 +0,0 @@
1
- import axios, { type AxiosInstance } from 'axios'
2
- import { loadConfig } from '../utils/config.js'
3
-
4
- export interface ApiClient {
5
- get<T>(endpoint: string): Promise<T>
6
- post<T>(endpoint: string, body: unknown): Promise<T>
7
- }
8
-
9
- export function createApiClient(): ApiClient {
10
- const config = loadConfig()
11
-
12
- const httpClient: AxiosInstance = axios.create({
13
- baseURL: config.host,
14
- headers: {
15
- Authorization: `Bearer ${config.token}`,
16
- 'Content-Type': 'application/json',
17
- 'User-Agent': 'fiftth-cli/0.1.0',
18
- },
19
- })
20
-
21
- return {
22
- async get<T>(endpoint: string): Promise<T> {
23
- const response = await httpClient.get<T>(endpoint)
24
- return response.data
25
- },
26
- async post<T>(endpoint: string, body: unknown): Promise<T> {
27
- const response = await httpClient.post<T>(endpoint, body)
28
- return response.data
29
- },
30
- }
31
- }
@@ -1,101 +0,0 @@
1
- import chalk from 'chalk'
2
- import { getRepoPath } from '../config/configService.js'
3
- import { getRuntimeSelectedTask } from '../context/runtimeContext.js'
4
- import {
5
- checkGitRepo,
6
- checkRepoExists,
7
- checkUncommittedChanges,
8
- checkoutBranch,
9
- fetch,
10
- } from '../git/gitService.js'
11
- import { moveTaskToHumanReview } from '../services/taskService.js'
12
- import { cmd, fail, info, kv, muted, ok, section } from '../utils/ui.js'
13
-
14
- export async function checkoutCommand(taskId: string): Promise<void> {
15
- const selectedTask = getRuntimeSelectedTask()
16
-
17
- if (!selectedTask) {
18
- console.log(`\n${fail('No task selected.')}\n`)
19
- console.log(`${muted(`Run ${cmd('fiftth tasks')} to select one.`)}\n`)
20
- process.exit(1)
21
- }
22
-
23
- if (selectedTask.taskId !== taskId) {
24
- console.log(`\n${fail('Task ID does not match the selected task.')}\n`)
25
- console.log(kv('Selected task ID', selectedTask.taskId))
26
- console.log(`${muted(`Run ${cmd('fiftth tasks')} and select task ${cmd(taskId)} first.`)}\n`)
27
- console.log()
28
- process.exit(1)
29
- }
30
-
31
- if (selectedTask.repositories.length === 0) {
32
- console.log(`\n${info('This task has no linked repositories.')}\n`)
33
- return
34
- }
35
-
36
- console.log(`\n${section(`Checking out task ${selectedTask.taskId} in linked repositories`)}\n`)
37
-
38
- const successfulRepositories: string[] = []
39
- const failedRepositories: string[] = []
40
-
41
- for (const taskRepository of selectedTask.repositories) {
42
- const repoName = taskRepository.fullName
43
- const repoPath = getRepoPath(repoName)
44
-
45
- console.log(kv('Repository', repoName))
46
-
47
- if (!repoPath) {
48
- console.log(` ${fail('Skipped: repository not configured locally.')}`)
49
- failedRepositories.push(repoName)
50
- continue
51
- }
52
-
53
- if (!checkRepoExists(repoPath)) {
54
- console.log(` ${fail('Skipped: repository path not found.')}`)
55
- failedRepositories.push(repoName)
56
- continue
57
- }
58
-
59
- if (!checkGitRepo(repoPath)) {
60
- console.log(` ${fail('Skipped: configured path is not a git repository.')}`)
61
- failedRepositories.push(repoName)
62
- continue
63
- }
64
-
65
- const hasUncommittedChanges = await checkUncommittedChanges(repoPath)
66
- if (hasUncommittedChanges) {
67
- console.log(` ${fail('Skipped: uncommitted changes detected.')}`)
68
- failedRepositories.push(repoName)
69
- continue
70
- }
71
-
72
- try {
73
- await fetch(repoPath)
74
- await checkoutBranch(repoPath, taskRepository.branch)
75
- console.log(` ${ok(`Checked out branch ${chalk.bold(taskRepository.branch)}`)}`)
76
- successfulRepositories.push(repoName)
77
- } catch {
78
- console.log(` ${fail('Failed: could not fetch/checkout branch.')}`)
79
- failedRepositories.push(repoName)
80
- }
81
- }
82
-
83
- console.log()
84
- console.log(ok(`Repositories checked out: ${successfulRepositories.length}`))
85
- if (failedRepositories.length > 0) {
86
- console.log(fail(`Repositories failed/skipped: ${failedRepositories.length}`))
87
- for (const repository of failedRepositories) {
88
- console.log(` ${muted('•')} ${repository}`)
89
- }
90
- }
91
-
92
- if (successfulRepositories.length > 0 && failedRepositories.length === 0) {
93
- try {
94
- await moveTaskToHumanReview(selectedTask.taskId)
95
- } catch {
96
- console.log(`\n${info('Could not move task to Human Review. Please update it manually.')}\n`)
97
- }
98
- }
99
-
100
- console.log(`\n${ok('Checkout complete')}\n`)
101
- }
@@ -1,145 +0,0 @@
1
- import chalk from 'chalk'
2
- import { input, password, confirm } from '@inquirer/prompts'
3
- import ora from 'ora'
4
- import { loadConfig, saveConfig } from '../utils/config.js'
5
- import { cmd, fail, info, kv, muted, ok, section, title } from '../utils/ui.js'
6
-
7
- interface LoginOptions {
8
- token?: string
9
- host?: string
10
- }
11
-
12
- function normalizeToken(rawToken: string): string {
13
- return rawToken.trim().replace(/^Bearer\s+/i, '')
14
- }
15
-
16
- async function validateToken(host: string, token: string): Promise<boolean> {
17
- try {
18
- const response = await fetch(`${host}/cli-tokens`, {
19
- headers: {
20
- Authorization: `Bearer ${token}`,
21
- 'User-Agent': 'fiftth-cli/0.1.0',
22
- },
23
- })
24
-
25
- if (!response.ok) {
26
- return false
27
- }
28
-
29
- const body = (await response.json().catch(() => null)) as
30
- | { valid?: boolean }
31
- | null
32
-
33
- return body?.valid === true
34
- } catch {
35
- return false
36
- }
37
- }
38
-
39
- export async function loginCommand(options: LoginOptions): Promise<void> {
40
- const config = loadConfig()
41
- const host = options.host ?? config.host
42
-
43
- console.log(`\n${title('Welcome to Fiftth')}\n`)
44
- console.log(muted('Orchestrate tasks, repos and agent workflows from your terminal.\n'))
45
-
46
- if (config.token && !options.token) {
47
- const alreadyLoggedIn = await confirm({
48
- message: chalk.yellow('You are already logged in. Validate login again?'),
49
- default: false,
50
- })
51
- if (!alreadyLoggedIn) {
52
- console.log(`\n${muted('No changes made.')}\n`)
53
- return
54
- }
55
- }
56
-
57
- let token: string
58
-
59
- if (options.token) {
60
- token = normalizeToken(options.token)
61
- } else {
62
- console.log(muted(`Generate an access token at: ${cmd(`${host}/cli-tokens`)}\n`))
63
- token = await password({
64
- message: 'Paste your access token:',
65
- mask: '*',
66
- })
67
-
68
- if (!token || token.trim() === '') {
69
- console.error(`\n${fail('Token cannot be empty.')}\n`)
70
- process.exit(1)
71
- }
72
- token = normalizeToken(token)
73
- }
74
-
75
- const spinner = ora('Validating credentials...').start()
76
-
77
- const valid = await validateToken(host, token)
78
-
79
- if (!valid) {
80
- spinner.fail(fail('Authentication failed.'))
81
- console.error(`\n${muted('Make sure your token is correct and has not expired.')}\n`)
82
- process.exit(1)
83
- }
84
-
85
- spinner.succeed(ok('Authentication validated.'))
86
-
87
- saveConfig({ ...config, token, host })
88
-
89
- console.log(`\n${section('Session ready')}`)
90
- console.log(kv('Host', cmd(host)))
91
- console.log(kv('Next', `run ${cmd('fiftth use <workspace>')}`))
92
- console.log()
93
- }
94
-
95
- export async function logoutCommand(): Promise<void> {
96
- const config = loadConfig()
97
-
98
- if (!config.token) {
99
- console.log(`\n${info('You are not currently logged in.')}\n`)
100
- return
101
- }
102
-
103
- const confirmed = await confirm({
104
- message: 'Are you sure you want to log out?',
105
- default: false,
106
- })
107
-
108
- if (!confirmed) {
109
- console.log(`\n${muted('Logout cancelled.')}\n`)
110
- return
111
- }
112
-
113
- const { token: _token, ...rest } = config
114
- saveConfig(rest as ReturnType<typeof loadConfig>)
115
-
116
- console.log(`\n${ok('Logged out successfully.')}\n`)
117
- }
118
-
119
- export async function loginStatusCommand(): Promise<void> {
120
- const config = loadConfig()
121
-
122
- if (!config.token) {
123
- console.log(`\n${info('Not logged in.')}\n`)
124
- console.log(`${muted(`Run ${cmd('fiftth login')} to authenticate.`)}\n`)
125
- return
126
- }
127
-
128
- const spinner = ora('Checking authentication status...').start()
129
- const valid = await validateToken(config.host, config.token)
130
-
131
- if (valid) {
132
- spinner.succeed(ok('Authenticated'))
133
- console.log(`\n${kv('Host', cmd(config.host))}`)
134
- if (config.workspace) {
135
- console.log(kv('Workspace', cmd(config.workspace)))
136
- }
137
- console.log()
138
- } else {
139
- spinner.fail(fail('Token is invalid or expired.'))
140
- console.log(`\n${muted(`Run ${cmd('fiftth login')} to authenticate again.`)}\n`)
141
- }
142
- }
143
-
144
- export { input }
145
- export { normalizeToken }
@@ -1,113 +0,0 @@
1
- import fs from 'fs'
2
- import os from 'os'
3
- import path from 'path'
4
- import chalk from 'chalk'
5
- import { Command } from 'commander'
6
- import {
7
- addRepository,
8
- getRepositories,
9
- removeRepository,
10
- } from '../config/configService.js'
11
- import { cmd, fail, info, kv, muted, ok, section } from '../utils/ui.js'
12
-
13
- function expandHomeDirectory(inputPath: string): string {
14
- if (inputPath === '~') {
15
- return os.homedir()
16
- }
17
-
18
- if (inputPath.startsWith('~/') || inputPath.startsWith('~\\')) {
19
- return path.join(os.homedir(), inputPath.slice(2))
20
- }
21
-
22
- return inputPath
23
- }
24
-
25
- function resolveRepoPath(inputPath: string): string {
26
- const expandedPath = expandHomeDirectory(inputPath)
27
- return path.resolve(expandedPath)
28
- }
29
-
30
- function validateRepositoryPath(repoPath: string): void {
31
- if (!fs.existsSync(repoPath) || !fs.statSync(repoPath).isDirectory()) {
32
- console.error(`\n${fail('Path not found.')}\n`)
33
- process.exit(1)
34
- }
35
-
36
- const gitPath = path.join(repoPath, '.git')
37
-
38
- if (!fs.existsSync(gitPath) || !fs.statSync(gitPath).isDirectory()) {
39
- console.error(`\n${fail('The provided path is not a git repository.')}\n`)
40
- process.exit(1)
41
- }
42
- }
43
-
44
- function addRepoCommand(repoName: string, repoPathInput: string | string[]): void {
45
- const normalizedPathInput = Array.isArray(repoPathInput)
46
- ? repoPathInput.join(' ')
47
- : repoPathInput
48
-
49
- const absolutePath = resolveRepoPath(normalizedPathInput)
50
- validateRepositoryPath(absolutePath)
51
-
52
- const { overwritten } = addRepository(repoName, absolutePath)
53
-
54
- if (overwritten) {
55
- console.log(chalk.yellow(`${info('Repository already exists. Overwriting mapping.')}`))
56
- }
57
-
58
- console.log(`\n${ok('Repository registered')}\n`)
59
- console.log(kv('Repo', repoName))
60
- console.log(kv('Path', cmd(absolutePath)))
61
- console.log()
62
- }
63
-
64
- function listRepoCommand(): void {
65
- const repositories = getRepositories()
66
- const entries = Object.entries(repositories)
67
-
68
- if (entries.length === 0) {
69
- console.log(`\n${info('No repositories configured yet.')}\n`)
70
- console.log(`${muted(`Use ${cmd('fiftth repo add <repoName> <path>')}`)}\n`)
71
- return
72
- }
73
-
74
- const longestNameLength = Math.max(...entries.map(([name]) => name.length))
75
-
76
- console.log(`\n${section('Configured repositories')}\n`)
77
- for (const [repoName, repoPath] of entries) {
78
- console.log(`${repoName.padEnd(longestNameLength, ' ')} ${muted('->')} ${cmd(repoPath)}`)
79
- }
80
- console.log()
81
- }
82
-
83
- function removeRepoCommand(repoName: string): void {
84
- const removed = removeRepository(repoName)
85
-
86
- if (!removed) {
87
- console.error(`\n${fail(`Repository '${repoName}' is not configured.`)}\n`)
88
- process.exit(1)
89
- }
90
-
91
- console.log(`\n${ok('Repository removed')}\n`)
92
- console.log(kv('Repo', repoName))
93
- console.log()
94
- }
95
-
96
- export function registerRepoCommands(program: Command): void {
97
- const repoCmd = program.command('repo').description('Manage local repository mappings')
98
-
99
- repoCmd
100
- .command('add <repoName> <path...>')
101
- .description('Register a local repository path')
102
- .action(addRepoCommand)
103
-
104
- repoCmd
105
- .command('list')
106
- .description('List all configured repositories')
107
- .action(listRepoCommand)
108
-
109
- repoCmd
110
- .command('remove <repoName>')
111
- .description('Remove a configured repository')
112
- .action(removeRepoCommand)
113
- }
@@ -1,86 +0,0 @@
1
- import chalk from 'chalk'
2
- import inquirer from 'inquirer'
3
- import { loadConfig } from '../utils/config.js'
4
- import { fetchActiveTasks, type Task } from '../services/taskService.js'
5
- import { setRuntimeSelectedTask } from '../context/runtimeContext.js'
6
- import { cmd, fail, info, kv, muted, ok, section } from '../utils/ui.js'
7
-
8
- function printTaskRepositories(task: Task): void {
9
- console.log(`\n${section(`Linked repositories for: ${task.title}`)}\n`)
10
- console.log(kv('Task ID', task.id))
11
- console.log('')
12
-
13
- for (const repository of task.repositories) {
14
- console.log(kv('Repository', repository.fullName))
15
- console.log(` ${muted('branch')} ${chalk.bold(repository.branch)}`)
16
- console.log('')
17
- }
18
- }
19
-
20
- export async function tasksCommand(): Promise<void> {
21
- const config = loadConfig()
22
-
23
- if (!config.token) {
24
- console.error(
25
- `\n${fail('Not authenticated.')} ${muted(`Run ${cmd('fiftth login')} first.`)}\n`,
26
- )
27
- process.exit(1)
28
- }
29
-
30
- if (!config.workspaceId) {
31
- console.error(
32
- `\n${fail('Workspace not selected.')} ${muted(`Run ${cmd('fiftth use')} first.`)}\n`,
33
- )
34
- process.exit(1)
35
- }
36
-
37
- let tasks: Task[]
38
- try {
39
- tasks = await fetchActiveTasks(config.workspaceId)
40
- } catch {
41
- console.error(`\n${fail('Failed to fetch tasks from API.')}\n`)
42
- process.exit(1)
43
- }
44
-
45
- if (tasks.length === 0) {
46
- console.log(`\n${info('No active tasks found.')}\n`)
47
- return
48
- }
49
-
50
- console.log(`\n${section(`Active tasks in ${config.workspace ?? 'workspace'}`)}\n`)
51
-
52
- const { selectedTaskId } = await inquirer.prompt<{ selectedTaskId: string }>([
53
- {
54
- type: 'list',
55
- name: 'selectedTaskId',
56
- message: 'Choose a task',
57
- choices: tasks.map((task) => ({
58
- name: `${task.title} (${task.id})`,
59
- value: task.id,
60
- })),
61
- },
62
- ])
63
-
64
- const selectedTask = tasks.find((task) => task.id === selectedTaskId)
65
-
66
- if (!selectedTask) {
67
- console.error(`\n${fail('No active tasks found.')}\n`)
68
- process.exit(1)
69
- }
70
-
71
- setRuntimeSelectedTask({
72
- taskId: selectedTask.id,
73
- title: selectedTask.title,
74
- repositories: selectedTask.repositories,
75
- })
76
-
77
- console.log(`\n${ok(`Task selected: ${chalk.bold(selectedTask.title)}`)}`)
78
- console.log(kv('Task ID', selectedTask.id))
79
-
80
- if (selectedTask.repositories.length === 0) {
81
- console.log(`${info('This task has no linked repositories.')}\n`)
82
- return
83
- }
84
-
85
- printTaskRepositories(selectedTask)
86
- }