@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,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
-
@@ -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
- }
@@ -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
- }
package/vitest.config.ts DELETED
@@ -1,8 +0,0 @@
1
- import { defineConfig } from 'vitest/config'
2
-
3
- export default defineConfig({
4
- test: {
5
- environment: 'node',
6
- globals: true,
7
- },
8
- })