kedovi 0.77.1 → 0.77.2

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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  `kedovi upgrade` 가 올린 버전 사이의 항목을 표로 보여 준다. 배포할 때 이번 버전 항목을 맨 위에 더한다 (`## 버전` 아래 `| 구분 | 내용 |` 표, 한 줄씩 짧게).
4
4
 
5
+ ## 0.77.2
6
+
7
+ | 구분 | 내용 |
8
+ |---|---|
9
+ | 수정 | `kedovi team whoami --refresh` 가 계정 입력 창을 띄우다 `TypeError (reading 'log')` 로 죽던 것 |
10
+ | 수정 | 화면 없는 실행(에이전트)에서 저장소 계정을 못 읽으면 이유(원격 없음 · gh · glab 로그인 · ssh 키 · git credential)를 한 줄 알림 |
11
+
5
12
  ## 0.77.1
6
13
 
7
14
  | 구분 | 내용 |
package/bin/kedovi.js CHANGED
@@ -79,8 +79,10 @@ const { spawnSync, execFileSync } = require('child_process');
79
79
  const kit = require('./kit.js');
80
80
  const gateway = require('./gateway.js');
81
81
 
82
- let p;
83
- const clack = () => (p = p || require('@clack/prompts'));
82
+ let clackMod;
83
+ const clack = () => (clackMod = clackMod || require('@clack/prompts'));
84
+ // p.log 등을 어디서 먼저 쓰든 화면 라이브러리를 불러온다 (늦게 불러오기 때문에 생기던 "undefined (reading 'log')" 방지)
85
+ const p = new Proxy({}, { get: (_, k) => clack()[k] });
84
86
 
85
87
  const BACK = Symbol('kedovi:back');
86
88
 
@@ -1620,32 +1622,39 @@ function requireAdmin(root, what, { soft = false } = {}) {
1620
1622
 
1621
1623
  // 이 PC 사용자 확인 — 워크스페이스 repo 원격의 저장소 계정 username. ssh 면 ssh -T 인사말, https 면 gh · glab · git credential 의 사용자 이름
1622
1624
  const NOT_USER = new Set(['oauth2', 'x-access-token', 'gitlab-ci-token', 'personalaccesstoken', 'token', 'git']);
1623
- function detectAccount(root) {
1625
+ /** why: 못 찾았을 때 이유를 쌓는다 (화면 없는 실행에서 한 줄로 알린다) */
1626
+ function detectAccount(root, why = []) {
1624
1627
  const url = (spawnSync('git', ['-C', root, 'remote', 'get-url', 'origin'], { encoding: 'utf8' }).stdout || '').trim();
1625
- if (!url) return null;
1628
+ if (!url) { why.push('워크스페이스 repo 에 origin 원격이 없음'); return null; }
1626
1629
  const ssh = url.match(/^ssh:\/\/([\w.-]+)@([^:/]+)(?::(\d+))?\//) || url.match(/^([\w.-]+)@([^:/]+):/);
1627
1630
  // 로그인 창 · 브라우저가 뜨지 않게 (Git Credential Manager · askpass)
1628
1631
  const env = { ...process.env, GIT_TERMINAL_PROMPT: '0', GCM_INTERACTIVE: 'never' };
1629
1632
  delete env.GIT_ASKPASS; delete env.SSH_ASKPASS;
1630
- const run = (cmd, args, input) => { try { const r = spawnSync(cmd, args, { encoding: 'utf8', timeout: 8000, input, env }); return `${r.stdout || ''}\n${r.stderr || ''}`; } catch { return ''; } };
1633
+ const missing = new Set();
1634
+ const run = (cmd, args, input) => { try { const r = spawnSync(cmd, args, { encoding: 'utf8', timeout: 8000, input, env }); if (r.error && r.error.code === 'ENOENT') missing.add(cmd); return `${r.stdout || ''}\n${r.stderr || ''}`; } catch { return ''; } };
1631
1635
  if (ssh && !/^https?:/.test(url)) {
1632
1636
  const [, user, host, port] = ssh;
1633
1637
  const out = run('ssh', ['-T', '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=5', '-o', 'StrictHostKeyChecking=accept-new', ...(port ? ['-p', port] : []), `${user}@${host}`]);
1634
1638
  const m = out.match(/Hi ([\w.-]+)!/) || out.match(/Welcome to GitLab, @([\w.-]+)!/) || out.match(/logged in as ([\w.-]+)/i);
1635
- return m ? { username: m[1], host, source: 'ssh' } : null;
1639
+ if (m) return { username: m[1], host, source: 'ssh' };
1640
+ why.push(`ssh -T ${user}@${host}${port ? ` -p ${port}` : ''} 이 계정 이름을 돌려주지 않음 (키 등록 · 접속 확인)`);
1641
+ return null;
1636
1642
  }
1637
1643
  let host = '';
1638
- try { host = new URL(url).hostname; } catch { return null; }
1644
+ try { host = new URL(url).hostname; } catch { why.push(`원격 주소를 읽지 못함 (${url})`); return null; }
1639
1645
  if (/github/.test(host)) {
1640
1646
  const u = run('gh', ['api', 'user', '--hostname', host, '--jq', '.login']).trim().split('\n')[0];
1641
1647
  if (/^[\w.-]+$/.test(u)) return { username: u, host, source: 'gh' };
1648
+ why.push(missing.has('gh') ? 'gh 없음' : `gh 로그인 안 됨 (gh auth login --hostname ${host})`);
1642
1649
  } else {
1643
1650
  const j = run('glab', ['api', 'user', '--hostname', host]);
1644
1651
  const m = j.match(/"username"\s*:\s*"([\w.-]+)"/);
1645
1652
  if (m) return { username: m[1], host, source: 'glab' };
1653
+ why.push(missing.has('glab') ? 'glab 없음' : `glab 로그인 안 됨 (glab auth login --hostname ${host})`);
1646
1654
  }
1647
1655
  const cred = run('git', ['-C', root, 'credential', 'fill'], `protocol=https\nhost=${host}\n\n`).match(/^username=(.+)$/m);
1648
1656
  if (cred && !NOT_USER.has(cred[1].trim().toLowerCase())) return { username: cred[1].trim(), host, source: 'git credential' };
1657
+ why.push(cred ? `git credential 의 사용자가 토큰 이름(${cred[1].trim()})` : 'git credential 에 저장된 사용자 없음');
1649
1658
  return null;
1650
1659
  }
1651
1660
 
@@ -1656,10 +1665,13 @@ async function ensureAccount(root, { refresh = false, ask = process.stdin.isTTY
1656
1665
  if (cur && cur.username && !refresh) return cur;
1657
1666
  // 못 찾은 기록이 있으면 7일 동안 다시 묻지 않는다 (start 마다 느려지지 않게. 다시: kedovi team whoami --refresh)
1658
1667
  if (cur && cur.source === 'none' && !refresh && (Date.now() - Date.parse(cur.checked || 0)) < 7 * 864e5) return null;
1659
- const found = detectAccount(root);
1668
+ const why = [];
1669
+ const found = detectAccount(root, why);
1660
1670
  let me = found ? { ...found, checked: today } : null;
1671
+ // 화면 없는 실행(에이전트 등)은 물을 수 없으니 이유를 한 줄 알린다
1672
+ if (!me && !ask) clack().log.warn(`저장소 계정을 읽지 못했습니다: ${why.join(' · ') || '이유 모름'}. 터미널에서 kedovi team whoami --refresh 를 직접 실행하면 입력해 적을 수 있습니다`);
1661
1673
  if (!me && ask) {
1662
- p.log.info('워크스페이스 원격에서 저장소 계정을 읽지 못했습니다. 팀장 · 담당 repo 판정에 쓰니 직접 적어 주세요 (나중에 다시 확인: kedovi team whoami --refresh)');
1674
+ clack().log.info('워크스페이스 원격에서 저장소 계정을 읽지 못했습니다. 팀장 · 담당 repo 판정에 쓰니 직접 적어 주세요 (나중에 다시 확인: kedovi team whoami --refresh)');
1663
1675
  const v = await clack().text({ message: '저장소 계정 username', placeholder: '예: hong01' });
1664
1676
  if (!clack().isCancel(v) && String(v || '').trim()) me = { username: String(v).trim(), host: '', source: 'input', checked: today };
1665
1677
  }
@@ -1682,7 +1694,7 @@ function adoptAccount(root, username) {
1682
1694
  const all = [...(t.leads || []), ...(t.admins || []), ...(t.members || [])];
1683
1695
  if (all.some((x) => sameUser(x, username)) || !all.some((x) => sameUser(x, gitName))) return;
1684
1696
  const lead = [...(t.leads || []), ...(t.admins || [])].some((x) => sameUser(x, gitName));
1685
- p.log.info(lead
1697
+ clack().log.info(lead
1686
1698
  ? `팀 설정의 팀장 ${gitName} 이(가) git user.name 으로 적혀 있습니다. 저장소 계정으로 바꾸세요: kedovi team lead add ${username} --nick <이름> 뒤 kedovi team lead remove ${gitName}`
1687
1699
  : `팀 설정에 ${gitName} 이(가) git user.name 으로 적혀 있습니다. 팀장에게 저장소 계정 ${username} 으로 바꿔 달라고 요청하세요 (kedovi team add ${username} --nick <이름> --repos …)`);
1688
1700
  }
@@ -1769,7 +1781,7 @@ async function cmdWhoami(root, args) {
1769
1781
  console.log(`이 PC 사용자: ${w.nickname ? `${w.nickname} · ` : ''}${w.username || '(모름)'} (확인: ${w.source})`);
1770
1782
  console.log(`역할: ${w.lead ? '팀장 (모든 repo · 설정 수정)' : w.person ? `팀원 · 담당 ${(w.repos || []).join(', ') || '없음'}` : '명단에 없음'}`);
1771
1783
  if (blocked.length) console.log(`고칠 수 없는 repo: ${blocked.join(', ')} (담당자 · 팀장에게 요청)`);
1772
- if (w.source === 'git user.name') console.log(' 저장소 계정을 아직 확인하지 못했습니다: kedovi team whoami --refresh');
1784
+ if (w.source === 'git user.name' && !args.includes('--refresh')) console.log(' 저장소 계정을 아직 확인하지 못했습니다: kedovi team whoami --refresh');
1773
1785
  }
1774
1786
 
1775
1787
  /** 팀장 · 팀원 · 담당 repo · 부를 이름 (workspace.json team.leads · members) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kedovi",
3
- "version": "0.77.1",
3
+ "version": "0.77.2",
4
4
  "private": false,
5
5
  "description": "Claude Code · Codex 겸용 AI 협업 워크스페이스 킷",
6
6
  "license": "MIT",