loopers-token-cli 0.1.0 → 0.2.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.
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
2
2
  import { createInterface } from 'node:readline/promises';
3
3
  import { stdin, stdout } from 'node:process';
4
4
  import { loadConfig, saveConfig, CONFIG_PATH } from '../config.js';
5
+ import { detectGithubUsername, isValidGithubUsername } from '../github.js';
5
6
  const DEFAULT_ENDPOINT = 'https://loopers-token-dashboard.vercel.app/api/ingest';
6
7
  /** 비대화형(플래그) 또는 대화형으로 config를 생성/갱신한다. deviceId는 1회 생성 후 고정. */
7
8
  export async function runInit(opts) {
@@ -9,31 +10,50 @@ export async function runInit(opts) {
9
10
  const rl = createInterface({ input: stdin, output: stdout });
10
11
  const ask = async (q, fallback) => {
11
12
  const suffix = fallback ? ` [${fallback}]` : '';
12
- const ans = (await rl.question(`${q}${suffix}: `)).trim();
13
- return ans || fallback || '';
13
+ try {
14
+ const ans = (await rl.question(`${q}${suffix}: `)).trim();
15
+ return ans || fallback || '';
16
+ }
17
+ catch {
18
+ return fallback || '';
19
+ }
14
20
  };
15
21
  try {
16
22
  console.log('\n루퍼스 토큰 대시보드 — 초기 설정');
17
- console.log('⚠️ 닉네임/팀에 실명·이메일 등 개인정보를 넣지 마세요. 자유 라벨만 사용합니다.\n');
18
- const nickname = opts.nickname ?? (await ask('닉네임', existing?.nickname));
23
+ console.log('⚠️ 라벨에 실명·이메일 등 개인정보를 넣지 마세요.\n');
24
+ // GitHub username 확보: 플래그 > 기존값 > gh 자동감지 > 수동입력
25
+ let username = opts.nickname ?? existing?.nickname;
26
+ if (!username) {
27
+ const detected = await detectGithubUsername();
28
+ if (detected) {
29
+ console.log(`🔎 gh CLI에서 GitHub username 감지: ${detected}`);
30
+ }
31
+ username = await ask('GitHub username', detected ?? undefined);
32
+ }
33
+ if (username && !isValidGithubUsername(username)) {
34
+ console.error(`\n❌ "${username}" 은(는) 올바른 GitHub username 형식이 아닙니다.`);
35
+ process.exitCode = 1;
36
+ return;
37
+ }
19
38
  const team = opts.team ?? (await ask('팀', existing?.team));
20
39
  const endpoint = opts.endpoint ??
21
40
  (await ask('서버 endpoint', existing?.endpoint ?? DEFAULT_ENDPOINT));
22
41
  const enrollKey = opts.enrollKey ?? (await ask('등록 키(enroll key)', existing?.enrollKey));
23
- if (!nickname || !team || !endpoint || !enrollKey) {
24
- console.error('\n❌ 닉네임/팀/endpoint/등록 키는 모두 필요합니다.');
42
+ if (!username || !team || !endpoint || !enrollKey) {
43
+ console.error('\n❌ GitHub username/팀/endpoint/등록 키는 모두 필요합니다.');
25
44
  process.exitCode = 1;
26
45
  return;
27
46
  }
28
47
  const config = {
29
48
  deviceId: existing?.deviceId ?? randomUUID(),
30
- nickname,
49
+ nickname: username,
31
50
  team,
32
51
  endpoint,
33
52
  enrollKey,
34
53
  };
35
54
  await saveConfig(config);
36
55
  console.log(`\n✅ 설정 저장 완료: ${CONFIG_PATH}`);
56
+ console.log(` GitHub username: ${config.nickname}`);
37
57
  console.log(` deviceId: ${config.deviceId}`);
38
58
  console.log(' 이제 `loopers-token sync` 로 전송하세요.\n');
39
59
  }
package/dist/github.js ADDED
@@ -0,0 +1,26 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ const execFileAsync = promisify(execFile);
4
+ /**
5
+ * 로컬 gh CLI(GitHub CLI)로 로그인된 username을 조회한다.
6
+ * gh 미설치/미로그인 등 어떤 이유로든 실패하면 null.
7
+ */
8
+ export async function detectGithubUsername() {
9
+ try {
10
+ const { stdout } = await execFileAsync('gh', ['api', 'user', '--jq', '.login'], {
11
+ timeout: 5000,
12
+ });
13
+ const login = stdout.trim();
14
+ return isValidGithubUsername(login) ? login : null;
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ }
20
+ /**
21
+ * GitHub username 형식 검증.
22
+ * 규칙: 영숫자와 하이픈, 1~39자, 하이픈으로 시작/끝 불가, 연속 하이픈 불가.
23
+ */
24
+ export function isValidGithubUsername(name) {
25
+ return /^[a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38}$/.test(name);
26
+ }
package/dist/index.js CHANGED
@@ -10,13 +10,14 @@ program
10
10
  .version(CLI_VERSION);
11
11
  program
12
12
  .command('init')
13
- .description('닉네임/팀/서버 설정 (deviceId는 1회 생성 후 고정)')
14
- .option('--nickname <name>', '닉네임 (실명/이메일 금지)')
13
+ .description('GitHub username/팀/서버 설정 (deviceId는 1회 생성 후 고정)')
14
+ .option('--github-username <name>', 'GitHub username (미지정 시 gh CLI 자동 감지)')
15
+ .option('--nickname <name>', '(별칭) --github-username 과 동일')
15
16
  .option('--team <team>', '팀 라벨')
16
17
  .option('--endpoint <url>', '서버 ingest endpoint')
17
18
  .option('--enroll-key <key>', '부트캠프 공통 등록 키')
18
19
  .action((opts) => runInit({
19
- nickname: opts.nickname,
20
+ nickname: opts.githubUsername ?? opts.nickname,
20
21
  team: opts.team,
21
22
  endpoint: opts.endpoint,
22
23
  enrollKey: opts.enrollKey,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopers-token-cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "루퍼스 토큰 대시보드 수집 CLI — ~/.claude 세션의 토큰 집계값만 전송",
5
5
  "type": "module",
6
6
  "bin": {