@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.
- package/.fiftthnexus/.github/workflows/copilot-orchestrator.yml +78 -0
- package/.fiftthnexus/actions/Dockerfile +34 -0
- package/.fiftthnexus/actions/copilot-agent.mjs +269 -0
- package/.fiftthnexus/actions/package.json +8 -0
- package/.fiftthnexus/orchestrator.ts +2304 -0
- package/.fiftthnexus/skills/env-implement-prompt.md +65 -0
- package/.fiftthnexus/skills/env-plan-prompt.md +33 -0
- package/.fiftthnexus/skills/env-review-prompt.md +61 -0
- package/.fiftthnexus/skills/grill-me.md +9 -0
- package/.fiftthnexus/skills/prd-to-issues.md +150 -0
- package/.fiftthnexus/skills/write-prd.md +70 -0
- package/README.md +216 -25
- package/dist/api/client.d.ts +6 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +13 -2
- package/dist/api/client.js.map +1 -1
- package/dist/commands/checkout.d.ts +6 -1
- package/dist/commands/checkout.d.ts.map +1 -1
- package/dist/commands/checkout.js +415 -44
- package/dist/commands/checkout.js.map +1 -1
- package/dist/commands/login.d.ts +0 -2
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +83 -32
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/model.d.ts +2 -0
- package/dist/commands/model.d.ts.map +1 -0
- package/dist/commands/model.js +32 -0
- package/dist/commands/model.js.map +1 -0
- package/dist/commands/planningContext.d.ts +6 -0
- package/dist/commands/planningContext.d.ts.map +1 -0
- package/dist/commands/planningContext.js +91 -0
- package/dist/commands/planningContext.js.map +1 -0
- package/dist/commands/repo.d.ts.map +1 -1
- package/dist/commands/repo.js +38 -15
- package/dist/commands/repo.js.map +1 -1
- package/dist/commands/skills.d.ts +2 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +123 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/use.d.ts +1 -5
- package/dist/commands/use.d.ts.map +1 -1
- package/dist/commands/use.js +63 -48
- package/dist/commands/use.js.map +1 -1
- package/dist/index.js +86 -27
- package/dist/index.js.map +1 -1
- package/dist/services/nexusService.d.ts +30 -0
- package/dist/services/nexusService.d.ts.map +1 -0
- package/dist/services/nexusService.js +188 -0
- package/dist/services/nexusService.js.map +1 -0
- package/dist/services/prdService.d.ts +12 -0
- package/dist/services/prdService.d.ts.map +1 -0
- package/dist/services/prdService.js +103 -0
- package/dist/services/prdService.js.map +1 -0
- package/dist/services/taskSelection.d.ts +10 -0
- package/dist/services/taskSelection.d.ts.map +1 -0
- package/dist/services/taskSelection.js +112 -0
- package/dist/services/taskSelection.js.map +1 -0
- package/dist/services/taskService.d.ts +23 -1
- package/dist/services/taskService.d.ts.map +1 -1
- package/dist/services/taskService.js +118 -12
- package/dist/services/taskService.js.map +1 -1
- package/dist/utils/config.d.ts +2 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +20 -3
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/dashboard.d.ts +65 -0
- package/dist/utils/dashboard.d.ts.map +1 -0
- package/dist/utils/dashboard.js +205 -0
- package/dist/utils/dashboard.js.map +1 -0
- package/dist/utils/models.d.ts +14 -0
- package/dist/utils/models.d.ts.map +1 -0
- package/dist/utils/models.js +89 -0
- package/dist/utils/models.js.map +1 -0
- package/dist/utils/ui.d.ts +6 -0
- package/dist/utils/ui.d.ts.map +1 -1
- package/dist/utils/ui.js +22 -1
- package/dist/utils/ui.js.map +1 -1
- package/dist/utils/version.d.ts +4 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +26 -0
- package/dist/utils/version.js.map +1 -0
- package/package.json +9 -4
- package/.github/workflows/publish-npm.yml +0 -62
- package/dist/commands/tasks.d.ts +0 -2
- package/dist/commands/tasks.d.ts.map +0 -1
- package/dist/commands/tasks.js +0 -69
- package/dist/commands/tasks.js.map +0 -1
- package/dist/context/runtimeContext.d.ts +0 -14
- package/dist/context/runtimeContext.d.ts.map +0 -1
- package/dist/context/runtimeContext.js +0 -21
- package/dist/context/runtimeContext.js.map +0 -1
- package/dist/services/taskContext.d.ts +0 -14
- package/dist/services/taskContext.d.ts.map +0 -1
- package/dist/services/taskContext.js +0 -15
- package/dist/services/taskContext.js.map +0 -1
- package/dist/utils/api.d.ts +0 -10
- package/dist/utils/api.d.ts.map +0 -1
- package/dist/utils/api.js +0 -25
- package/dist/utils/api.js.map +0 -1
- package/src/api/client.ts +0 -31
- package/src/commands/checkout.ts +0 -101
- package/src/commands/login.ts +0 -145
- package/src/commands/repo.ts +0 -113
- package/src/commands/tasks.ts +0 -86
- package/src/commands/use.ts +0 -149
- package/src/config/configService.ts +0 -56
- package/src/context/runtimeContext.ts +0 -42
- package/src/git/gitService.ts +0 -29
- package/src/index.ts +0 -133
- package/src/services/taskContext.ts +0 -32
- package/src/services/taskService.ts +0 -53
- package/src/utils/api.ts +0 -41
- package/src/utils/config.ts +0 -48
- package/src/utils/ui.ts +0 -46
- package/tsconfig.json +0 -18
- package/vitest.config.ts +0 -8
package/src/commands/use.ts
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
import { select } from '@inquirer/prompts'
|
|
3
|
-
import ora from 'ora'
|
|
4
|
-
import { loadConfig, saveConfig } from '../utils/config.js'
|
|
5
|
-
import { createApiClient, type Workspace } from '../utils/api.js'
|
|
6
|
-
import { cmd, fail, info, kv, muted, ok, section } from '../utils/ui.js'
|
|
7
|
-
|
|
8
|
-
interface UseOptions {
|
|
9
|
-
list?: boolean
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export async function useCommand(
|
|
13
|
-
workspace: string | undefined,
|
|
14
|
-
options: UseOptions,
|
|
15
|
-
): Promise<void> {
|
|
16
|
-
const config = loadConfig()
|
|
17
|
-
|
|
18
|
-
if (!config.token) {
|
|
19
|
-
console.error(
|
|
20
|
-
`\n${fail('Not authenticated.')} ${muted(`Run ${cmd('fiftth login')} first.`)}\n`,
|
|
21
|
-
)
|
|
22
|
-
process.exit(1)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const client = createApiClient()
|
|
26
|
-
|
|
27
|
-
if (options.list) {
|
|
28
|
-
await listWorkspaces(client)
|
|
29
|
-
return
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (workspace) {
|
|
33
|
-
await switchWorkspace(workspace, config, client)
|
|
34
|
-
} else {
|
|
35
|
-
await interactiveSelectWorkspace(config, client)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function listWorkspaces(
|
|
40
|
-
client: ReturnType<typeof createApiClient>,
|
|
41
|
-
): Promise<void> {
|
|
42
|
-
const spinner = ora('Fetching workspaces…').start()
|
|
43
|
-
|
|
44
|
-
let workspaces: Workspace[]
|
|
45
|
-
try {
|
|
46
|
-
workspaces = await client.getWorkspaces()
|
|
47
|
-
} catch (err) {
|
|
48
|
-
spinner.fail(fail('Failed to fetch workspaces.'))
|
|
49
|
-
console.error(`\n${muted(err instanceof Error ? err.message : String(err))}\n`)
|
|
50
|
-
process.exit(1)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
spinner.stop()
|
|
54
|
-
|
|
55
|
-
if (workspaces.length === 0) {
|
|
56
|
-
console.log(`\n${info('No workspaces found.')}\n`)
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const config = loadConfig()
|
|
61
|
-
|
|
62
|
-
console.log(`\n${section('Available workspaces')}\n`)
|
|
63
|
-
for (const ws of workspaces) {
|
|
64
|
-
const active = ws.slug === config.workspace
|
|
65
|
-
const marker = active ? chalk.green('✓') : chalk.dim('·')
|
|
66
|
-
const name = active ? chalk.bold(ws.name) : ws.name
|
|
67
|
-
console.log(` ${marker} ${name} ${muted(ws.slug)}`)
|
|
68
|
-
}
|
|
69
|
-
console.log()
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async function switchWorkspace(
|
|
73
|
-
slug: string,
|
|
74
|
-
config: ReturnType<typeof loadConfig>,
|
|
75
|
-
client: ReturnType<typeof createApiClient>,
|
|
76
|
-
): Promise<void> {
|
|
77
|
-
const spinner = ora('Fetching workspaces...').start()
|
|
78
|
-
|
|
79
|
-
let workspaces: Workspace[]
|
|
80
|
-
try {
|
|
81
|
-
workspaces = await client.getWorkspaces()
|
|
82
|
-
} catch (err) {
|
|
83
|
-
spinner.fail(fail('Failed to fetch workspaces.'))
|
|
84
|
-
console.error(`\n${muted(err instanceof Error ? err.message : String(err))}\n`)
|
|
85
|
-
process.exit(1)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
spinner.stop()
|
|
89
|
-
|
|
90
|
-
const match = workspaces.find(
|
|
91
|
-
(ws) => ws.slug === slug || ws.name === slug,
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
if (!match) {
|
|
95
|
-
console.error(`\n${fail(`Workspace ${chalk.bold(slug)} not found.`)}\n`)
|
|
96
|
-
console.log(`${muted(`Run ${cmd('fiftth use --list')} to see available workspaces.`)}\n`)
|
|
97
|
-
process.exit(1)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
saveConfig({
|
|
101
|
-
...config,
|
|
102
|
-
workspace: match.slug,
|
|
103
|
-
workspaceId: match.id,
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
console.log(`\n${ok(`Now using workspace ${chalk.bold(match.name)} ${muted(`(${match.slug})`)}`)}`)
|
|
107
|
-
console.log(`${kv('Workspace ID', match.id)}\n`)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async function interactiveSelectWorkspace(
|
|
111
|
-
config: ReturnType<typeof loadConfig>,
|
|
112
|
-
client: ReturnType<typeof createApiClient>,
|
|
113
|
-
): Promise<void> {
|
|
114
|
-
const spinner = ora('Fetching workspaces...').start()
|
|
115
|
-
|
|
116
|
-
let workspaces: Workspace[]
|
|
117
|
-
try {
|
|
118
|
-
workspaces = await client.getWorkspaces()
|
|
119
|
-
} catch (err) {
|
|
120
|
-
spinner.fail(fail('Failed to fetch workspaces.'))
|
|
121
|
-
console.error(`\n${muted(err instanceof Error ? err.message : String(err))}\n`)
|
|
122
|
-
process.exit(1)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
spinner.stop()
|
|
126
|
-
|
|
127
|
-
if (workspaces.length === 0) {
|
|
128
|
-
console.log(`\n${info('No workspaces found.')}\n`)
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const chosen = await select<string>({
|
|
133
|
-
message: 'Select a workspace',
|
|
134
|
-
choices: workspaces.map((ws) => ({
|
|
135
|
-
value: ws.slug,
|
|
136
|
-
name: `${ws.name} ${chalk.dim(ws.slug)}`,
|
|
137
|
-
})),
|
|
138
|
-
default: config.workspace,
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
const match = workspaces.find((ws) => ws.slug === chosen)!
|
|
142
|
-
saveConfig({
|
|
143
|
-
...config,
|
|
144
|
-
workspace: chosen,
|
|
145
|
-
workspaceId: match.id,
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
console.log(`\n${ok(`Now using workspace ${chalk.bold(match.name)} ${muted(`(${match.slug})`)}`)}\n`)
|
|
149
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import Conf from 'conf'
|
|
2
|
-
|
|
3
|
-
interface LocalCliConfig {
|
|
4
|
-
repositories: Record<string, string>
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
const config = new Conf<LocalCliConfig>({
|
|
8
|
-
projectName: 'fiftth-cli',
|
|
9
|
-
defaults: {
|
|
10
|
-
repositories: {},
|
|
11
|
-
},
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
export function getRepositories(): Record<string, string> {
|
|
15
|
-
return config.get('repositories')
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function findRepositoryKey(repoName: string): string | undefined {
|
|
19
|
-
const repositories = getRepositories()
|
|
20
|
-
const exactMatch = Object.prototype.hasOwnProperty.call(repositories, repoName)
|
|
21
|
-
|
|
22
|
-
if (exactMatch) {
|
|
23
|
-
return repoName
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const normalizedRepoName = repoName.toLowerCase()
|
|
27
|
-
return Object.keys(repositories).find((name) => name.toLowerCase() === normalizedRepoName)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function getRepoPath(repoName: string): string | undefined {
|
|
31
|
-
const repositoryKey = findRepositoryKey(repoName)
|
|
32
|
-
if (!repositoryKey) {
|
|
33
|
-
return undefined
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return getRepositories()[repositoryKey]
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function addRepository(repoName: string, repoPath: string): { overwritten: boolean } {
|
|
40
|
-
const repositories = getRepositories()
|
|
41
|
-
const overwritten = Boolean(repositories[repoName])
|
|
42
|
-
|
|
43
|
-
config.set(`repositories.${repoName}`, repoPath)
|
|
44
|
-
|
|
45
|
-
return { overwritten }
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function removeRepository(repoName: string): boolean {
|
|
49
|
-
const repositoryKey = findRepositoryKey(repoName)
|
|
50
|
-
if (!repositoryKey) {
|
|
51
|
-
return false
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
config.delete(`repositories.${repositoryKey}`)
|
|
55
|
-
return true
|
|
56
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import Conf from 'conf'
|
|
2
|
-
|
|
3
|
-
interface SelectedTaskRepository {
|
|
4
|
-
fullName: string
|
|
5
|
-
branch: string
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface SelectedTaskContext {
|
|
9
|
-
taskId: string
|
|
10
|
-
title: string
|
|
11
|
-
repositories: SelectedTaskRepository[]
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface RuntimeContextStore {
|
|
15
|
-
selectedTask?: SelectedTaskContext
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const contextStore = new Conf<RuntimeContextStore>({
|
|
19
|
-
projectName: 'fiftth-cli',
|
|
20
|
-
defaults: {},
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
let selectedTaskInMemory: SelectedTaskContext | null = null
|
|
24
|
-
|
|
25
|
-
export function setRuntimeSelectedTask(task: SelectedTaskContext): void {
|
|
26
|
-
selectedTaskInMemory = task
|
|
27
|
-
contextStore.set('selectedTask', task)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function getRuntimeSelectedTask(): SelectedTaskContext | null {
|
|
31
|
-
if (selectedTaskInMemory) {
|
|
32
|
-
return selectedTaskInMemory
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return contextStore.get('selectedTask') ?? null
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function clearRuntimeSelectedTask(): void {
|
|
39
|
-
selectedTaskInMemory = null
|
|
40
|
-
contextStore.delete('selectedTask')
|
|
41
|
-
}
|
|
42
|
-
|
package/src/git/gitService.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import fs from 'fs'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import { simpleGit } from 'simple-git'
|
|
4
|
-
|
|
5
|
-
export function checkRepoExists(repoPath: string): boolean {
|
|
6
|
-
return fs.existsSync(repoPath) && fs.statSync(repoPath).isDirectory()
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function checkGitRepo(repoPath: string): boolean {
|
|
10
|
-
const gitPath = path.join(repoPath, '.git')
|
|
11
|
-
return fs.existsSync(gitPath) && fs.statSync(gitPath).isDirectory()
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export async function checkUncommittedChanges(repoPath: string): Promise<boolean> {
|
|
15
|
-
const git = simpleGit(repoPath)
|
|
16
|
-
const status = await git.status()
|
|
17
|
-
|
|
18
|
-
return !status.isClean()
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export async function fetch(repoPath: string): Promise<void> {
|
|
22
|
-
const git = simpleGit(repoPath)
|
|
23
|
-
await git.fetch()
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function checkoutBranch(repoPath: string, branch: string): Promise<void> {
|
|
27
|
-
const git = simpleGit(repoPath)
|
|
28
|
-
await git.checkout(branch)
|
|
29
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander'
|
|
3
|
-
import chalk from 'chalk'
|
|
4
|
-
import { loginCommand, logoutCommand, loginStatusCommand } from './commands/login.js'
|
|
5
|
-
import { useCommand } from './commands/use.js'
|
|
6
|
-
import { registerRepoCommands } from './commands/repo.js'
|
|
7
|
-
import { tasksCommand } from './commands/tasks.js'
|
|
8
|
-
import { checkoutCommand } from './commands/checkout.js'
|
|
9
|
-
import { loadConfig, getConfigPath } from './utils/config.js'
|
|
10
|
-
import { cmd, fail, kv, muted, ok, section, title } from './utils/ui.js'
|
|
11
|
-
|
|
12
|
-
const program = new Command()
|
|
13
|
-
|
|
14
|
-
program
|
|
15
|
-
.name('fiftth')
|
|
16
|
-
.description('Startup-grade CLI for autonomous software teams')
|
|
17
|
-
.version('0.1.0', '-v, --version', 'Output the current version')
|
|
18
|
-
.addHelpText(
|
|
19
|
-
'before',
|
|
20
|
-
`\n${title('Fiftth CLI')}\n${muted('start orchestrating your AI engineering workflows.')}\n`,
|
|
21
|
-
)
|
|
22
|
-
.addHelpText(
|
|
23
|
-
'after',
|
|
24
|
-
`
|
|
25
|
-
${chalk.dim('Examples:')}
|
|
26
|
-
${cmd('$ fiftth login')} Authenticate with Fiftth
|
|
27
|
-
${cmd('$ fiftth login --token <tok>')} Authenticate using a token directly
|
|
28
|
-
${cmd('$ fiftth use my-workspace')} Switch to a workspace
|
|
29
|
-
${cmd('$ fiftth use')} Interactively select a workspace
|
|
30
|
-
${cmd('$ fiftth use --list')} List all available workspaces
|
|
31
|
-
${cmd('$ fiftth repo add api ~/dev/api')} Register a local git repository
|
|
32
|
-
${cmd('$ fiftth repo list')} List configured repositories
|
|
33
|
-
${cmd('$ fiftth tasks')} Select an active task and inspect repos
|
|
34
|
-
${cmd('$ fiftth checkout <taskId>')} Checkout task branches in all linked repositories
|
|
35
|
-
`,
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
// ─── fiftth login ──────────────────────────────────────────────────────────
|
|
39
|
-
const loginCmd = program
|
|
40
|
-
.command('login')
|
|
41
|
-
.description('Authenticate with the Fiftth platform')
|
|
42
|
-
.option('-t, --token <token>', 'Access token (for non-interactive / CI use)')
|
|
43
|
-
.option('--host <host>', 'API host (defaults to https://api.fiftth.io)')
|
|
44
|
-
.addHelpText(
|
|
45
|
-
'after',
|
|
46
|
-
`
|
|
47
|
-
${chalk.dim('Examples:')}
|
|
48
|
-
${chalk.cyan('$ fiftth login')}
|
|
49
|
-
${chalk.cyan('$ fiftth login --token ghp_xxxxxxxxxxxx')}
|
|
50
|
-
${chalk.cyan('$ fiftth login --host https://selfhosted.example.com')}
|
|
51
|
-
`,
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
loginCmd
|
|
55
|
-
.command('status')
|
|
56
|
-
.description('Show current authentication status')
|
|
57
|
-
.action(async () => {
|
|
58
|
-
await loginStatusCommand().catch(handleError)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
loginCmd
|
|
62
|
-
.command('logout')
|
|
63
|
-
.description('Log out and remove stored credentials')
|
|
64
|
-
.action(async () => {
|
|
65
|
-
await logoutCommand().catch(handleError)
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
loginCmd.action(async (options: { token?: string; host?: string }) => {
|
|
69
|
-
await loginCommand(options).catch(handleError)
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
// ─── fiftth use ────────────────────────────────────────────────────────────
|
|
73
|
-
program
|
|
74
|
-
.command('use [workspace]')
|
|
75
|
-
.description('Select the active workspace')
|
|
76
|
-
.option('-l, --list', 'List all available workspaces')
|
|
77
|
-
.addHelpText(
|
|
78
|
-
'after',
|
|
79
|
-
`
|
|
80
|
-
${chalk.dim('Examples:')}
|
|
81
|
-
${chalk.cyan('$ fiftth use')} Interactively select a workspace
|
|
82
|
-
${chalk.cyan('$ fiftth use my-workspace')} Switch directly to a workspace by slug or name
|
|
83
|
-
${chalk.cyan('$ fiftth use --list')} List all workspaces
|
|
84
|
-
`,
|
|
85
|
-
)
|
|
86
|
-
.action(async (workspace: string | undefined, options: { list?: boolean }) => {
|
|
87
|
-
await useCommand(workspace, options).catch(handleError)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
// ─── fiftth config ─────────────────────────────────────────────────────────
|
|
91
|
-
program
|
|
92
|
-
.command('config')
|
|
93
|
-
.description('Show current CLI configuration')
|
|
94
|
-
.action(() => {
|
|
95
|
-
const config = loadConfig()
|
|
96
|
-
console.log(`\n${section('CLI configuration')}\n`)
|
|
97
|
-
console.log(kv('Config file', cmd(getConfigPath())))
|
|
98
|
-
console.log(kv('Host', cmd(config.host)))
|
|
99
|
-
console.log(kv('Workspace', config.workspace ? cmd(config.workspace) : muted('(none)')))
|
|
100
|
-
console.log(kv('Logged in', config.token ? ok('yes') : fail('no')))
|
|
101
|
-
console.log()
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
// ─── fiftth repo ───────────────────────────────────────────────────────────
|
|
105
|
-
registerRepoCommands(program)
|
|
106
|
-
|
|
107
|
-
// ─── fiftth tasks ──────────────────────────────────────────────────────────
|
|
108
|
-
program
|
|
109
|
-
.command('tasks')
|
|
110
|
-
.description('List and select active tasks')
|
|
111
|
-
.action(async () => {
|
|
112
|
-
await tasksCommand().catch(handleError)
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
// ─── fiftth checkout ───────────────────────────────────────────────────────
|
|
116
|
-
program
|
|
117
|
-
.command('checkout <taskId>')
|
|
118
|
-
.description('Checkout the selected task branches for all linked repositories')
|
|
119
|
-
.action(async (taskId: string) => {
|
|
120
|
-
await checkoutCommand(taskId).catch(handleError)
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
// ─── Error handling ────────────────────────────────────────────────────────
|
|
124
|
-
function handleError(err: unknown): void {
|
|
125
|
-
if (err instanceof Error) {
|
|
126
|
-
console.error(`\n${fail(`Error: ${err.message}`)}\n`)
|
|
127
|
-
} else {
|
|
128
|
-
console.error(`\n${fail('An unexpected error occurred.')}\n`)
|
|
129
|
-
}
|
|
130
|
-
process.exit(1)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
program.parse()
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export interface SelectedTaskRepository {
|
|
2
|
-
repoName: string
|
|
3
|
-
branch: string
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export interface SelectedTaskContext {
|
|
7
|
-
taskId: string
|
|
8
|
-
title: string
|
|
9
|
-
repositories: SelectedTaskRepository[]
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
let selectedTaskContext: SelectedTaskContext | null = null
|
|
13
|
-
|
|
14
|
-
export function setSelectedTaskContext(task: SelectedTaskContext): void {
|
|
15
|
-
selectedTaskContext = task
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function getSelectedTaskContext(): SelectedTaskContext | null {
|
|
19
|
-
return selectedTaskContext
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function getSelectedTaskBranch(repoName: string): string | undefined {
|
|
23
|
-
const repository = selectedTaskContext?.repositories.find(
|
|
24
|
-
(entry) => entry.repoName === repoName,
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
return repository?.branch
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function clearSelectedTaskContext(): void {
|
|
31
|
-
selectedTaskContext = null
|
|
32
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { createApiClient } from '../api/client.js'
|
|
2
|
-
|
|
3
|
-
const ALLOWED_COLUMNS = new Set(['IN_PROGRESS', 'HUMAN_REVIEW'])
|
|
4
|
-
|
|
5
|
-
interface ApiTaskRepository {
|
|
6
|
-
fullName: string
|
|
7
|
-
githubPrBranch: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface ApiTask {
|
|
11
|
-
id: string
|
|
12
|
-
title: string
|
|
13
|
-
column: string
|
|
14
|
-
repositories?: ApiTaskRepository[]
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface TaskRepository {
|
|
18
|
-
fullName: string
|
|
19
|
-
branch: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface Task {
|
|
23
|
-
id: string
|
|
24
|
-
title: string
|
|
25
|
-
column: string
|
|
26
|
-
repositories: TaskRepository[]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export async function moveTaskToHumanReview(taskId: string): Promise<void> {
|
|
30
|
-
const client = createApiClient()
|
|
31
|
-
await client.post(`/tasks/${taskId}/move`, {
|
|
32
|
-
targetColumn: 'HUMAN_REVIEW',
|
|
33
|
-
targetOrder: 0,
|
|
34
|
-
})
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export async function fetchActiveTasks(workspaceId: string): Promise<Task[]> {
|
|
38
|
-
const client = createApiClient()
|
|
39
|
-
const query = new URLSearchParams({ workspaceId }).toString()
|
|
40
|
-
const tasks = await client.get<ApiTask[]>(`/tasks?${query}`)
|
|
41
|
-
|
|
42
|
-
return tasks
|
|
43
|
-
.filter((task) => ALLOWED_COLUMNS.has(task.column))
|
|
44
|
-
.map((task) => ({
|
|
45
|
-
id: task.id,
|
|
46
|
-
title: task.title,
|
|
47
|
-
column: task.column,
|
|
48
|
-
repositories: (task.repositories ?? []).map((repository) => ({
|
|
49
|
-
fullName: repository.fullName,
|
|
50
|
-
branch: repository.githubPrBranch,
|
|
51
|
-
})),
|
|
52
|
-
}))
|
|
53
|
-
}
|
package/src/utils/api.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { loadConfig } from './config.js'
|
|
2
|
-
|
|
3
|
-
export interface Workspace {
|
|
4
|
-
id: string
|
|
5
|
-
name: string
|
|
6
|
-
slug: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface ApiClient {
|
|
10
|
-
getWorkspaces(): Promise<Workspace[]>
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function createApiClient(): ApiClient {
|
|
14
|
-
const config = loadConfig()
|
|
15
|
-
|
|
16
|
-
async function request<T>(endpoint: string): Promise<T> {
|
|
17
|
-
const url = `${config.host}${endpoint}`
|
|
18
|
-
const response = await fetch(url, {
|
|
19
|
-
headers: {
|
|
20
|
-
Authorization: `Bearer ${config.token}`,
|
|
21
|
-
'Content-Type': 'application/json',
|
|
22
|
-
'User-Agent': 'fiftth-cli/0.1.0',
|
|
23
|
-
},
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
if (!response.ok) {
|
|
27
|
-
const body = await response.text().catch(() => '')
|
|
28
|
-
throw new Error(
|
|
29
|
-
`API request failed: ${response.status} ${response.statusText}${body ? ` — ${body}` : ''}`,
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return response.json() as Promise<T>
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
getWorkspaces(): Promise<Workspace[]> {
|
|
38
|
-
return request<Workspace[]>('/workspaces')
|
|
39
|
-
},
|
|
40
|
-
}
|
|
41
|
-
}
|
package/src/utils/config.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import os from 'os'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import fs from 'fs'
|
|
4
|
-
|
|
5
|
-
export interface Config {
|
|
6
|
-
token?: string
|
|
7
|
-
host: string
|
|
8
|
-
workspace?: string
|
|
9
|
-
workspaceId?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const CONFIG_DIR = path.join(os.homedir(), '.fiftth')
|
|
13
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json')
|
|
14
|
-
|
|
15
|
-
const DEFAULT_CONFIG: Config = {
|
|
16
|
-
host: 'https://fiftth.com/api',
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function loadConfig(): Config {
|
|
20
|
-
try {
|
|
21
|
-
if (!fs.existsSync(CONFIG_FILE)) {
|
|
22
|
-
return { ...DEFAULT_CONFIG }
|
|
23
|
-
}
|
|
24
|
-
const raw = fs.readFileSync(CONFIG_FILE, 'utf-8')
|
|
25
|
-
return { ...DEFAULT_CONFIG, ...JSON.parse(raw) }
|
|
26
|
-
} catch {
|
|
27
|
-
return { ...DEFAULT_CONFIG }
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function saveConfig(config: Config): void {
|
|
32
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
33
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 })
|
|
34
|
-
}
|
|
35
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), {
|
|
36
|
-
mode: 0o600,
|
|
37
|
-
encoding: 'utf-8',
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function isAuthenticated(): boolean {
|
|
42
|
-
const config = loadConfig()
|
|
43
|
-
return Boolean(config.token)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function getConfigPath(): string {
|
|
47
|
-
return CONFIG_FILE
|
|
48
|
-
}
|
package/src/utils/ui.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
|
|
3
|
-
export const BRAND_COLOR = '#C8A96A'
|
|
4
|
-
|
|
5
|
-
export const ICONS = {
|
|
6
|
-
brand: '◆',
|
|
7
|
-
success: '✓',
|
|
8
|
-
error: '✕',
|
|
9
|
-
info: 'ℹ',
|
|
10
|
-
arrow: '→',
|
|
11
|
-
spark: '✦',
|
|
12
|
-
} as const
|
|
13
|
-
|
|
14
|
-
export const brand = chalk.hex(BRAND_COLOR)
|
|
15
|
-
|
|
16
|
-
export function cmd(text: string): string {
|
|
17
|
-
return brand(text)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function title(text: string): string {
|
|
21
|
-
return `${brand(ICONS.brand)} ${chalk.bold(text)}`
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function ok(text: string): string {
|
|
25
|
-
return `${chalk.green(ICONS.success)} ${text}`
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function fail(text: string): string {
|
|
29
|
-
return `${chalk.red(ICONS.error)} ${text}`
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function info(text: string): string {
|
|
33
|
-
return `${brand(ICONS.info)} ${text}`
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function muted(text: string): string {
|
|
37
|
-
return chalk.dim(text)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function section(text: string): string {
|
|
41
|
-
return `${brand(ICONS.spark)} ${chalk.bold(text)}`
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function kv(key: string, value: string): string {
|
|
45
|
-
return `${brand(ICONS.arrow)} ${chalk.bold(`${key}:`)} ${value}`
|
|
46
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"outDir": "dist",
|
|
7
|
-
"rootDir": "src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"forceConsistentCasingInFileNames": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"declaration": true,
|
|
13
|
-
"declarationMap": true,
|
|
14
|
-
"sourceMap": true
|
|
15
|
-
},
|
|
16
|
-
"include": ["src/**/*"],
|
|
17
|
-
"exclude": ["node_modules", "dist", "tests"]
|
|
18
|
-
}
|