@fiftth/fiftth-cli 0.1.0 → 1.0.0

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 (40) hide show
  1. package/.github/workflows/publish-npm.yml +62 -0
  2. package/README.md +123 -111
  3. package/dist/api/client.d.ts +1 -0
  4. package/dist/api/client.d.ts.map +1 -1
  5. package/dist/api/client.js +4 -0
  6. package/dist/api/client.js.map +1 -1
  7. package/dist/commands/checkout.d.ts +1 -1
  8. package/dist/commands/checkout.d.ts.map +1 -1
  9. package/dist/commands/checkout.js +63 -31
  10. package/dist/commands/checkout.js.map +1 -1
  11. package/dist/commands/tasks.d.ts.map +1 -1
  12. package/dist/commands/tasks.js +4 -1
  13. package/dist/commands/tasks.js.map +1 -1
  14. package/dist/config/configService.d.ts.map +1 -1
  15. package/dist/config/configService.js +17 -4
  16. package/dist/config/configService.js.map +1 -1
  17. package/dist/index.js +25 -25
  18. package/dist/index.js.map +1 -1
  19. package/dist/services/taskService.d.ts +1 -0
  20. package/dist/services/taskService.d.ts.map +1 -1
  21. package/dist/services/taskService.js +7 -0
  22. package/dist/services/taskService.js.map +1 -1
  23. package/package.json +56 -56
  24. package/src/api/client.ts +31 -26
  25. package/src/commands/checkout.ts +101 -68
  26. package/src/commands/login.ts +145 -145
  27. package/src/commands/repo.ts +113 -113
  28. package/src/commands/tasks.ts +86 -83
  29. package/src/commands/use.ts +149 -149
  30. package/src/config/configService.ts +56 -40
  31. package/src/context/runtimeContext.ts +42 -42
  32. package/src/git/gitService.ts +29 -29
  33. package/src/index.ts +133 -133
  34. package/src/services/taskContext.ts +32 -32
  35. package/src/services/taskService.ts +53 -45
  36. package/src/utils/api.ts +41 -41
  37. package/src/utils/config.ts +48 -48
  38. package/src/utils/ui.ts +46 -46
  39. package/tsconfig.json +18 -18
  40. package/vitest.config.ts +8 -8
@@ -1,145 +1,145 @@
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
+ 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 +1,113 @@
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
+ 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
+ }