@gitping/cli 0.0.1 → 0.1.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.
- package/README.md +300 -0
- package/dist/index.js +32041 -73342
- package/package.json +23 -7
- package/src/api/client.ts +0 -53
- package/src/api/pings.ts +0 -79
- package/src/api/threads.ts +0 -14
- package/src/api/users.ts +0 -43
- package/src/auth/keychain.ts +0 -28
- package/src/auth/pkce.ts +0 -24
- package/src/commands/accept.ts +0 -43
- package/src/commands/block.ts +0 -48
- package/src/commands/chat.ts +0 -181
- package/src/commands/ignore.ts +0 -43
- package/src/commands/inbox.ts +0 -98
- package/src/commands/leaderboard.ts +0 -51
- package/src/commands/login.ts +0 -117
- package/src/commands/logout.ts +0 -29
- package/src/commands/ping.ts +0 -57
- package/src/commands/search.ts +0 -64
- package/src/commands/status.ts +0 -116
- package/src/commands/whoami.ts +0 -57
- package/src/config.ts +0 -39
- package/src/index.ts +0 -147
- package/src/ui/ChatView.tsx +0 -195
- package/src/ui/InboxWatch.tsx +0 -134
- package/tsconfig.json +0 -21
package/src/commands/inbox.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
import ora from 'ora'
|
|
3
|
-
import { listPings } from '../api/pings.ts'
|
|
4
|
-
import { getToken } from '../auth/keychain.ts'
|
|
5
|
-
import type { PingStatus } from '@gitping/shared'
|
|
6
|
-
import type { PingWithThread } from '../api/pings.ts'
|
|
7
|
-
|
|
8
|
-
interface InboxOptions {
|
|
9
|
-
unread?: boolean
|
|
10
|
-
watch?: boolean
|
|
11
|
-
json?: boolean
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export async function inboxCommand(options: InboxOptions): Promise<void> {
|
|
15
|
-
const token = await getToken()
|
|
16
|
-
if (!token) {
|
|
17
|
-
console.error(chalk.red('Not logged in. Run `gitping login` first.'))
|
|
18
|
-
throw Object.assign(new Error('Not authenticated'), { exitCode: 2 })
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (options.watch) {
|
|
22
|
-
// Lazy-load ink to prevent startup crash on React/reconciler version conflicts
|
|
23
|
-
const { render } = await import('ink')
|
|
24
|
-
const React = await import('react')
|
|
25
|
-
const { default: InboxWatch } = await import('../ui/InboxWatch.tsx')
|
|
26
|
-
render(React.createElement(InboxWatch, { token }))
|
|
27
|
-
return
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const filter = options.unread ? 'unread' : undefined
|
|
31
|
-
const spinner = ora('Loading inbox…').start()
|
|
32
|
-
|
|
33
|
-
let page
|
|
34
|
-
try {
|
|
35
|
-
page = await listPings({ filter })
|
|
36
|
-
} catch (err) {
|
|
37
|
-
spinner.fail('Failed to load inbox')
|
|
38
|
-
throw Object.assign(err instanceof Error ? err : new Error(String(err)), { exitCode: 3 })
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
spinner.stop()
|
|
42
|
-
|
|
43
|
-
if (options.json) {
|
|
44
|
-
console.log(JSON.stringify(page))
|
|
45
|
-
return
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (page.data.length === 0) {
|
|
49
|
-
console.log(chalk.dim(' No pings yet. Your inbox is empty.'))
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
console.log()
|
|
54
|
-
console.log(chalk.cyan.bold(' ⚡ GitPing Inbox') + chalk.dim(` (${page.data.length} pings)`))
|
|
55
|
-
console.log(chalk.dim(' ─────────────────────────────────────────'))
|
|
56
|
-
console.log()
|
|
57
|
-
for (const ping of page.data) {
|
|
58
|
-
printPing(ping)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (page.nextCursor) {
|
|
62
|
-
console.log(chalk.dim(`\n More results available. Use --cursor ${page.nextCursor}`))
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function printPing(ping: PingWithThread): void {
|
|
67
|
-
const statusColor = STATUS_COLORS[ping.status] ?? chalk.white
|
|
68
|
-
const ts = new Date(ping.created_at).toLocaleString()
|
|
69
|
-
const idShort = ping.id.slice(0, 8)
|
|
70
|
-
|
|
71
|
-
// Status badge with visual indicator
|
|
72
|
-
const statusBadge = statusColor(`[${ping.status}]`)
|
|
73
|
-
const categoryBadge = chalk.yellow(`[${ping.category}]`)
|
|
74
|
-
|
|
75
|
-
console.log(
|
|
76
|
-
` ${chalk.dim(idShort)} ` +
|
|
77
|
-
`${chalk.cyan.bold('@' + ping.sender_username)} ${chalk.dim('→')} ${chalk.cyan.bold('@' + ping.recipient_username)} ` +
|
|
78
|
-
`${statusBadge} ${categoryBadge}`,
|
|
79
|
-
)
|
|
80
|
-
console.log(` ${chalk.dim('│')} ${ping.message}`)
|
|
81
|
-
console.log(` ${chalk.dim('│')} ${chalk.dim(ts)}`)
|
|
82
|
-
if (ping.status === 'delivered') {
|
|
83
|
-
console.log(
|
|
84
|
-
` ${chalk.dim('└')} ${chalk.green('gitping accept @' + ping.sender_username)} · ` +
|
|
85
|
-
`${chalk.dim('gitping ignore @' + ping.sender_username)} · ` +
|
|
86
|
-
`${chalk.red('gitping block @' + ping.sender_username)}`,
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
|
-
console.log()
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const STATUS_COLORS: Record<PingStatus, (s: string) => string> = {
|
|
93
|
-
pending: chalk.gray,
|
|
94
|
-
delivered: chalk.blue,
|
|
95
|
-
accepted: chalk.green,
|
|
96
|
-
ignored: chalk.yellow,
|
|
97
|
-
blocked: chalk.red,
|
|
98
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
import ora from 'ora'
|
|
3
|
-
import { getLeaderboard } from '../api/users.ts'
|
|
4
|
-
|
|
5
|
-
interface LeaderboardOptions {
|
|
6
|
-
rising?: boolean
|
|
7
|
-
json?: boolean
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export async function leaderboardCommand(options: LeaderboardOptions): Promise<void> {
|
|
11
|
-
const label = options.rising ? 'Rising developers this week' : 'Most pinged developers'
|
|
12
|
-
const spinner = ora(`Loading ${label.toLowerCase()}…`).start()
|
|
13
|
-
|
|
14
|
-
let entries
|
|
15
|
-
try {
|
|
16
|
-
entries = await getLeaderboard(options.rising)
|
|
17
|
-
} catch (err) {
|
|
18
|
-
spinner.fail('Failed to load leaderboard')
|
|
19
|
-
throw Object.assign(err instanceof Error ? err : new Error(String(err)), { exitCode: 3 })
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
spinner.stop()
|
|
23
|
-
|
|
24
|
-
if (options.json) {
|
|
25
|
-
console.log(JSON.stringify(entries))
|
|
26
|
-
return
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
console.log()
|
|
30
|
-
console.log(chalk.bold(` ${label}`))
|
|
31
|
-
console.log(chalk.dim(' ─────────────────────────────────────────'))
|
|
32
|
-
console.log()
|
|
33
|
-
|
|
34
|
-
if (entries.length === 0) {
|
|
35
|
-
console.log(chalk.dim(' No data yet.'))
|
|
36
|
-
return
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
entries.forEach((entry, idx) => {
|
|
40
|
-
const rank = String(idx + 1).padStart(2)
|
|
41
|
-
const score = String(entry.rep_score).padStart(3)
|
|
42
|
-
const tier = entry.rep_tier.padEnd(6)
|
|
43
|
-
console.log(
|
|
44
|
-
` ${chalk.dim(rank + '.')} ${chalk.bold('@' + entry.github_username.padEnd(20))} ` +
|
|
45
|
-
`${chalk.cyan(score + ' rep')} ${chalk.yellow('[' + tier + ']')} ` +
|
|
46
|
-
`${chalk.dim(entry.ping_count + ' pings')}`,
|
|
47
|
-
)
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
console.log()
|
|
51
|
-
}
|
package/src/commands/login.ts
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import http from 'http'
|
|
2
|
-
import chalk from 'chalk'
|
|
3
|
-
import ora from 'ora'
|
|
4
|
-
import { createClient } from '../api/client.ts'
|
|
5
|
-
import { setToken } from '../auth/keychain.ts'
|
|
6
|
-
import { setUsername, getGithubClientId } from '../config.ts'
|
|
7
|
-
import { generateCodeVerifier, generateCodeChallenge, generateState } from '../auth/pkce.ts'
|
|
8
|
-
|
|
9
|
-
interface ExchangeResponse {
|
|
10
|
-
token: string
|
|
11
|
-
username: string
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface LoginOptions {
|
|
15
|
-
json?: boolean
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function loginCommand(options: LoginOptions): Promise<void> {
|
|
19
|
-
const clientId = getGithubClientId()
|
|
20
|
-
if (!clientId) {
|
|
21
|
-
console.error(chalk.red('Error: GITHUB_CLIENT_ID is not configured.'))
|
|
22
|
-
console.error(
|
|
23
|
-
chalk.dim('Set GITHUB_CLIENT_ID env var or run: gitping config set githubClientId <id>'),
|
|
24
|
-
)
|
|
25
|
-
throw Object.assign(new Error('GITHUB_CLIENT_ID not configured'), { exitCode: 1 })
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const codeVerifier = generateCodeVerifier()
|
|
29
|
-
const codeChallenge = generateCodeChallenge(codeVerifier)
|
|
30
|
-
const state = generateState()
|
|
31
|
-
|
|
32
|
-
const callbackPort = 3456
|
|
33
|
-
const redirectUri = `http://127.0.0.1:${callbackPort}/callback`
|
|
34
|
-
|
|
35
|
-
// Start local callback server
|
|
36
|
-
const server = await new Promise<http.Server>((resolve, reject) => {
|
|
37
|
-
const s = http.createServer()
|
|
38
|
-
s.listen(callbackPort, '127.0.0.1', () => resolve(s))
|
|
39
|
-
s.on('error', reject)
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
const callbackPromise = new Promise<{ code: string; state: string }>((resolve, reject) => {
|
|
43
|
-
server.on('request', (req, res) => {
|
|
44
|
-
const url = new URL(req.url ?? '/', `http://localhost`)
|
|
45
|
-
const code = url.searchParams.get('code')
|
|
46
|
-
const st = url.searchParams.get('state')
|
|
47
|
-
const error = url.searchParams.get('error')
|
|
48
|
-
|
|
49
|
-
res.writeHead(200, { 'Content-Type': 'text/html' })
|
|
50
|
-
res.end('<html><body><h2>Login successful — you can close this tab.</h2></body></html>')
|
|
51
|
-
server.close()
|
|
52
|
-
|
|
53
|
-
if (error || !code || !st) {
|
|
54
|
-
reject(new Error(error ?? 'OAuth callback missing required parameters'))
|
|
55
|
-
} else {
|
|
56
|
-
resolve({ code, state: st })
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
const authUrl = new URL('https://github.com/login/oauth/authorize')
|
|
62
|
-
authUrl.searchParams.set('client_id', clientId)
|
|
63
|
-
authUrl.searchParams.set('redirect_uri', redirectUri)
|
|
64
|
-
authUrl.searchParams.set('scope', 'read:user')
|
|
65
|
-
authUrl.searchParams.set('state', state)
|
|
66
|
-
authUrl.searchParams.set('code_challenge', codeChallenge)
|
|
67
|
-
authUrl.searchParams.set('code_challenge_method', 'S256')
|
|
68
|
-
|
|
69
|
-
console.log(chalk.cyan('Opening browser for GitHub login…'))
|
|
70
|
-
console.log(chalk.dim(`If the browser does not open, visit:\n ${authUrl.toString()}`))
|
|
71
|
-
|
|
72
|
-
const { default: open } = await import('open')
|
|
73
|
-
await open(authUrl.toString())
|
|
74
|
-
|
|
75
|
-
const spinner = ora('Waiting for GitHub authorization…').start()
|
|
76
|
-
|
|
77
|
-
let callbackResult: { code: string; state: string }
|
|
78
|
-
try {
|
|
79
|
-
callbackResult = await callbackPromise
|
|
80
|
-
} catch (err) {
|
|
81
|
-
spinner.fail('GitHub authorization failed')
|
|
82
|
-
throw Object.assign(err instanceof Error ? err : new Error(String(err)), { exitCode: 2 })
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (callbackResult.state !== state) {
|
|
86
|
-
spinner.fail('State mismatch — possible CSRF attack. Aborting.')
|
|
87
|
-
throw Object.assign(new Error('OAuth state mismatch'), { exitCode: 2 })
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
spinner.text = 'Exchanging code for token…'
|
|
91
|
-
|
|
92
|
-
const client = await createClient()
|
|
93
|
-
let result: ExchangeResponse
|
|
94
|
-
try {
|
|
95
|
-
result = await client
|
|
96
|
-
.post('v1/auth/cli/exchange', {
|
|
97
|
-
json: {
|
|
98
|
-
code: callbackResult.code,
|
|
99
|
-
code_verifier: codeVerifier,
|
|
100
|
-
redirect_uri: redirectUri,
|
|
101
|
-
},
|
|
102
|
-
})
|
|
103
|
-
.json<ExchangeResponse>()
|
|
104
|
-
} catch (err) {
|
|
105
|
-
spinner.fail('Token exchange failed')
|
|
106
|
-
throw Object.assign(err instanceof Error ? err : new Error(String(err)), { exitCode: 2 })
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
await setToken(result.token)
|
|
110
|
-
setUsername(result.username)
|
|
111
|
-
|
|
112
|
-
spinner.succeed(chalk.green(`Logged in as ${chalk.bold('@' + result.username)}`))
|
|
113
|
-
|
|
114
|
-
if (options.json) {
|
|
115
|
-
console.log(JSON.stringify({ username: result.username }))
|
|
116
|
-
}
|
|
117
|
-
}
|
package/src/commands/logout.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
import ora from 'ora'
|
|
3
|
-
import { createClient } from '../api/client.ts'
|
|
4
|
-
import { deleteToken } from '../auth/keychain.ts'
|
|
5
|
-
import { clearConfig } from '../config.ts'
|
|
6
|
-
|
|
7
|
-
interface LogoutOptions {
|
|
8
|
-
json?: boolean
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export async function logoutCommand(options: LogoutOptions): Promise<void> {
|
|
12
|
-
const spinner = ora('Logging out…').start()
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
const client = await createClient()
|
|
16
|
-
await client.post('v1/auth/logout').json()
|
|
17
|
-
} catch {
|
|
18
|
-
// Best-effort server-side logout — we still clear local credentials
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
await deleteToken()
|
|
22
|
-
clearConfig()
|
|
23
|
-
|
|
24
|
-
spinner.succeed('Logged out successfully')
|
|
25
|
-
|
|
26
|
-
if (options.json) {
|
|
27
|
-
console.log(JSON.stringify({ success: true }))
|
|
28
|
-
}
|
|
29
|
-
}
|
package/src/commands/ping.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
import ora from 'ora'
|
|
3
|
-
import { sendPing } from '../api/pings.ts'
|
|
4
|
-
import { getToken } from '../auth/keychain.ts'
|
|
5
|
-
import { PingCategory } from '@gitping/shared'
|
|
6
|
-
|
|
7
|
-
interface PingOptions {
|
|
8
|
-
cat?: string
|
|
9
|
-
json?: boolean
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const VALID_CATEGORIES = Object.values(PingCategory) as string[]
|
|
13
|
-
|
|
14
|
-
export async function pingCommand(
|
|
15
|
-
rawRecipient: string,
|
|
16
|
-
message: string,
|
|
17
|
-
options: PingOptions,
|
|
18
|
-
): Promise<void> {
|
|
19
|
-
const token = await getToken()
|
|
20
|
-
if (!token) {
|
|
21
|
-
console.error(chalk.red('Not logged in. Run `gitping login` first.'))
|
|
22
|
-
throw Object.assign(new Error('Not authenticated'), { exitCode: 2 })
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Strip leading @ if present
|
|
26
|
-
const recipient = rawRecipient.startsWith('@') ? rawRecipient.slice(1) : rawRecipient
|
|
27
|
-
|
|
28
|
-
const category = options.cat ?? PingCategory.COLLAB
|
|
29
|
-
if (!VALID_CATEGORIES.includes(category)) {
|
|
30
|
-
console.error(
|
|
31
|
-
chalk.red(`Invalid category "${category}". Valid values: ${VALID_CATEGORIES.join(', ')}`),
|
|
32
|
-
)
|
|
33
|
-
throw Object.assign(new Error('Invalid category'), { exitCode: 1 })
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const spinner = ora(`Pinging @${recipient}…`).start()
|
|
37
|
-
|
|
38
|
-
let ping
|
|
39
|
-
try {
|
|
40
|
-
ping = await sendPing({ recipient, message, category })
|
|
41
|
-
} catch (err) {
|
|
42
|
-
spinner.fail('Failed to send ping')
|
|
43
|
-
throw Object.assign(err instanceof Error ? err : new Error(String(err)), { exitCode: 3 })
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
spinner.succeed(
|
|
47
|
-
ping.status === 'delivered'
|
|
48
|
-
? chalk.green(`Ping delivered to @${recipient} (${chalk.dim(ping.id.slice(0, 8))})`)
|
|
49
|
-
: chalk.yellow(
|
|
50
|
-
`Ping queued for @${recipient} — they'll receive it when they join (${chalk.dim(ping.id.slice(0, 8))})`,
|
|
51
|
-
),
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
if (options.json) {
|
|
55
|
-
console.log(JSON.stringify(ping))
|
|
56
|
-
}
|
|
57
|
-
}
|
package/src/commands/search.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
import ora from 'ora'
|
|
3
|
-
import { getUser } from '../api/users.ts'
|
|
4
|
-
|
|
5
|
-
interface SearchOptions {
|
|
6
|
-
json?: boolean
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export async function searchCommand(rawUsername: string, options: SearchOptions): Promise<void> {
|
|
10
|
-
const username = rawUsername.startsWith('@') ? rawUsername.slice(1) : rawUsername
|
|
11
|
-
|
|
12
|
-
const spinner = ora(`Searching for @${username}…`).start()
|
|
13
|
-
|
|
14
|
-
let profile
|
|
15
|
-
try {
|
|
16
|
-
profile = await getUser(username)
|
|
17
|
-
} catch (err) {
|
|
18
|
-
const typed = err as { code?: string }
|
|
19
|
-
if (typed.code === 'NOT_FOUND') {
|
|
20
|
-
spinner.fail(`User @${username} not found on GitPing.`)
|
|
21
|
-
throw Object.assign(new Error('User not found'), { exitCode: 1 })
|
|
22
|
-
}
|
|
23
|
-
spinner.fail('Search failed')
|
|
24
|
-
throw Object.assign(err instanceof Error ? err : new Error(String(err)), { exitCode: 3 })
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
spinner.stop()
|
|
28
|
-
|
|
29
|
-
if (options.json) {
|
|
30
|
-
console.log(JSON.stringify(profile))
|
|
31
|
-
return
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
console.log()
|
|
35
|
-
console.log(chalk.bold(`@${profile.github_username}`))
|
|
36
|
-
if (profile.bio) console.log(chalk.dim(profile.bio))
|
|
37
|
-
console.log()
|
|
38
|
-
console.log(` ${chalk.cyan('Availability:')} ${formatAvailability(profile.availability)}`)
|
|
39
|
-
if (profile.availability_msg) {
|
|
40
|
-
console.log(` ${chalk.cyan('Status:')} ${profile.availability_msg}`)
|
|
41
|
-
}
|
|
42
|
-
console.log(` ${chalk.cyan('Rep tier:')} ${formatTier(profile.rep_tier)}`)
|
|
43
|
-
console.log()
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function formatAvailability(mode: string): string {
|
|
47
|
-
const map: Record<string, string> = {
|
|
48
|
-
open_collab: chalk.green('Open to collab'),
|
|
49
|
-
open_work: chalk.green('Open to work'),
|
|
50
|
-
selective: chalk.yellow('Selective'),
|
|
51
|
-
heads_down: chalk.red('Heads down'),
|
|
52
|
-
}
|
|
53
|
-
return map[mode] ?? mode
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function formatTier(tier: string): string {
|
|
57
|
-
const map: Record<string, string> = {
|
|
58
|
-
LOW: chalk.red('LOW'),
|
|
59
|
-
MEDIUM: chalk.yellow('MEDIUM'),
|
|
60
|
-
HIGH: chalk.green('HIGH'),
|
|
61
|
-
TOP: chalk.cyan('TOP ⭐'),
|
|
62
|
-
}
|
|
63
|
-
return map[tier] ?? tier
|
|
64
|
-
}
|
package/src/commands/status.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
import ora from 'ora'
|
|
3
|
-
import { getMe, updateMe } from '../api/users.ts'
|
|
4
|
-
import { getToken } from '../auth/keychain.ts'
|
|
5
|
-
import { Availability } from '@gitping/shared'
|
|
6
|
-
|
|
7
|
-
interface StatusOptions {
|
|
8
|
-
set?: string
|
|
9
|
-
message?: string
|
|
10
|
-
json?: boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const AVAILABILITY_DISPLAY: Record<string, string> = {
|
|
14
|
-
open_collab: 'open-to-collab',
|
|
15
|
-
open_work: 'open-to-work',
|
|
16
|
-
selective: 'selective',
|
|
17
|
-
heads_down: 'heads-down',
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const AVAILABILITY_FROM_INPUT: Record<string, Availability> = {
|
|
21
|
-
'open-to-collab': Availability.OPEN_COLLAB,
|
|
22
|
-
'open-to-work': Availability.OPEN_WORK,
|
|
23
|
-
selective: Availability.SELECTIVE,
|
|
24
|
-
'heads-down': Availability.HEADS_DOWN,
|
|
25
|
-
// also accept raw enum values
|
|
26
|
-
open_collab: Availability.OPEN_COLLAB,
|
|
27
|
-
open_work: Availability.OPEN_WORK,
|
|
28
|
-
heads_down: Availability.HEADS_DOWN,
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function statusCommand(options: StatusOptions): Promise<void> {
|
|
32
|
-
const token = await getToken()
|
|
33
|
-
if (!token) {
|
|
34
|
-
console.error(chalk.red('Not logged in. Run `gitping login` first.'))
|
|
35
|
-
throw Object.assign(new Error('Not authenticated'), { exitCode: 2 })
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (options.set || options.message) {
|
|
39
|
-
await setStatus(options)
|
|
40
|
-
} else {
|
|
41
|
-
await showStatus(options)
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function showStatus(options: StatusOptions): Promise<void> {
|
|
46
|
-
const spinner = ora('Fetching status…').start()
|
|
47
|
-
|
|
48
|
-
let user
|
|
49
|
-
try {
|
|
50
|
-
user = await getMe()
|
|
51
|
-
} catch (err) {
|
|
52
|
-
spinner.fail('Failed to fetch status')
|
|
53
|
-
throw Object.assign(err instanceof Error ? err : new Error(String(err)), { exitCode: 3 })
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
spinner.stop()
|
|
57
|
-
|
|
58
|
-
if (options.json) {
|
|
59
|
-
console.log(
|
|
60
|
-
JSON.stringify({
|
|
61
|
-
availability: user.availability,
|
|
62
|
-
availability_msg: user.availability_msg,
|
|
63
|
-
}),
|
|
64
|
-
)
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const displayMode = AVAILABILITY_DISPLAY[user.availability] ?? user.availability
|
|
69
|
-
console.log(`\n ${chalk.cyan('Mode:')} ${chalk.bold(displayMode)}`)
|
|
70
|
-
if (user.availability_msg) {
|
|
71
|
-
console.log(` ${chalk.cyan('Message:')} ${user.availability_msg}`)
|
|
72
|
-
}
|
|
73
|
-
console.log()
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function setStatus(options: StatusOptions): Promise<void> {
|
|
77
|
-
const update: Record<string, string> = {}
|
|
78
|
-
|
|
79
|
-
if (options.set) {
|
|
80
|
-
const mode = AVAILABILITY_FROM_INPUT[options.set]
|
|
81
|
-
if (!mode) {
|
|
82
|
-
console.error(
|
|
83
|
-
chalk.red(
|
|
84
|
-
`Invalid mode "${options.set}". Valid values: open-to-collab, open-to-work, selective, heads-down`,
|
|
85
|
-
),
|
|
86
|
-
)
|
|
87
|
-
throw Object.assign(new Error('Invalid availability mode'), { exitCode: 1 })
|
|
88
|
-
}
|
|
89
|
-
update['availability'] = mode
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (options.message !== undefined) {
|
|
93
|
-
update['availability_msg'] = options.message
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const spinner = ora('Updating status…').start()
|
|
97
|
-
|
|
98
|
-
let user
|
|
99
|
-
try {
|
|
100
|
-
user = await updateMe(update)
|
|
101
|
-
} catch (err) {
|
|
102
|
-
spinner.fail('Failed to update status')
|
|
103
|
-
throw Object.assign(err instanceof Error ? err : new Error(String(err)), { exitCode: 3 })
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
spinner.succeed(chalk.green('Status updated.'))
|
|
107
|
-
|
|
108
|
-
if (options.json) {
|
|
109
|
-
console.log(
|
|
110
|
-
JSON.stringify({
|
|
111
|
-
availability: user.availability,
|
|
112
|
-
availability_msg: user.availability_msg,
|
|
113
|
-
}),
|
|
114
|
-
)
|
|
115
|
-
}
|
|
116
|
-
}
|
package/src/commands/whoami.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk'
|
|
2
|
-
import ora from 'ora'
|
|
3
|
-
import { getMe } from '../api/users.ts'
|
|
4
|
-
import { getToken } from '../auth/keychain.ts'
|
|
5
|
-
|
|
6
|
-
interface WhoamiOptions {
|
|
7
|
-
json?: boolean
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export async function whoamiCommand(options: WhoamiOptions): Promise<void> {
|
|
11
|
-
const token = await getToken()
|
|
12
|
-
if (!token) {
|
|
13
|
-
console.error(chalk.red('Not logged in. Run `gitping login` first.'))
|
|
14
|
-
throw Object.assign(new Error('Not authenticated'), { exitCode: 2 })
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const spinner = ora('Fetching profile…').start()
|
|
18
|
-
|
|
19
|
-
let user
|
|
20
|
-
try {
|
|
21
|
-
user = await getMe()
|
|
22
|
-
} catch (err) {
|
|
23
|
-
spinner.fail('Failed to fetch profile')
|
|
24
|
-
throw Object.assign(err instanceof Error ? err : new Error(String(err)), { exitCode: 3 })
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
spinner.stop()
|
|
28
|
-
|
|
29
|
-
if (options.json) {
|
|
30
|
-
console.log(JSON.stringify(user))
|
|
31
|
-
return
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
console.log()
|
|
35
|
-
console.log(chalk.bold(`@${user.github_username}`))
|
|
36
|
-
if (user.bio) console.log(chalk.dim(user.bio))
|
|
37
|
-
console.log()
|
|
38
|
-
console.log(` ${chalk.cyan('Availability:')} ${formatAvailability(user.availability)}`)
|
|
39
|
-
if (user.availability_msg) {
|
|
40
|
-
console.log(` ${chalk.cyan('Status:')} ${user.availability_msg}`)
|
|
41
|
-
}
|
|
42
|
-
console.log(` ${chalk.cyan('Rep score:')} ${user.rep_score}/100`)
|
|
43
|
-
if (user.suspended) {
|
|
44
|
-
console.log(` ${chalk.red('⚠ Account suspended')}`)
|
|
45
|
-
}
|
|
46
|
-
console.log()
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function formatAvailability(mode: string): string {
|
|
50
|
-
const map: Record<string, string> = {
|
|
51
|
-
open_collab: chalk.green('Open to collab'),
|
|
52
|
-
open_work: chalk.green('Open to work'),
|
|
53
|
-
selective: chalk.yellow('Selective'),
|
|
54
|
-
heads_down: chalk.red('Heads down'),
|
|
55
|
-
}
|
|
56
|
-
return map[mode] ?? mode
|
|
57
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import Conf from 'conf'
|
|
2
|
-
|
|
3
|
-
interface CliConfig {
|
|
4
|
-
apiBaseUrl: string
|
|
5
|
-
githubClientId: string
|
|
6
|
-
username: string | undefined
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const DEFAULT_API_BASE_URL = 'https://gitping.onrender.com'
|
|
10
|
-
const DEFAULT_GITHUB_CLIENT_ID = 'Ov23lidHtFLHfZozhh8w'
|
|
11
|
-
|
|
12
|
-
export const conf = new Conf<CliConfig>({
|
|
13
|
-
projectName: 'gitping',
|
|
14
|
-
defaults: {
|
|
15
|
-
apiBaseUrl: DEFAULT_API_BASE_URL,
|
|
16
|
-
githubClientId: DEFAULT_GITHUB_CLIENT_ID,
|
|
17
|
-
username: undefined,
|
|
18
|
-
},
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
export function getApiBaseUrl(): string {
|
|
22
|
-
return (process.env['GITPING_API_URL'] ?? conf.get('apiBaseUrl')).replace(/\/$/, '')
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function getGithubClientId(): string {
|
|
26
|
-
return process.env['GITHUB_CLIENT_ID'] ?? conf.get('githubClientId')
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function getUsername(): string | undefined {
|
|
30
|
-
return conf.get('username')
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function setUsername(username: string): void {
|
|
34
|
-
conf.set('username', username)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function clearConfig(): void {
|
|
38
|
-
conf.clear()
|
|
39
|
-
}
|