guideline-manage 0.15.0 → 0.16.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.
@@ -107,10 +107,6 @@ export async function listFromServer({
107
107
  err(t('list.noServer'));
108
108
  return 2;
109
109
  }
110
- if (!token) {
111
- err(t('common.loginNeeded', { url }));
112
- return 1;
113
- }
114
110
  let src;
115
111
  try {
116
112
  src = await loadSource(remoteClient({ server: url, token, fetchImpl }));
@@ -14,6 +14,36 @@ async function readAll(stream) {
14
14
  return s;
15
15
  }
16
16
 
17
+ /**
18
+ * 터미널에서 토큰 한 줄을 읽는다. 입력한 글자는 화면에 보이지 않는다.
19
+ * 파이프가 아닌 터미널은 EOF 가 오지 않아서, readAll 로는 아무 안내 없이 멈춘 것처럼 보인다.
20
+ * @returns {Promise<string | null>} Ctrl-C 면 null
21
+ */
22
+ function readHiddenLine(stdin, write) {
23
+ return new Promise(resolve => {
24
+ let s = '';
25
+ const done = value => {
26
+ stdin.removeListener('data', onData);
27
+ stdin.setRawMode?.(false);
28
+ stdin.pause();
29
+ write('\n');
30
+ resolve(value);
31
+ };
32
+ const onData = chunk => {
33
+ for (const ch of String(chunk)) {
34
+ if (ch === '\r' || ch === '\n' || ch === '\u0004') return done(s);
35
+ if (ch === '\u0003') return done(null);
36
+ if (ch === '\u007f' || ch === '\b') s = s.slice(0, -1);
37
+ else s += ch;
38
+ }
39
+ };
40
+ stdin.setRawMode?.(true);
41
+ stdin.setEncoding?.('utf8');
42
+ stdin.on('data', onData);
43
+ stdin.resume();
44
+ });
45
+ }
46
+
17
47
  /** 127.0.0.1 의 임시 포트에서 /cb 한 번을 기다린다. */
18
48
  async function callbackServer() {
19
49
  let resolve;
@@ -45,6 +75,7 @@ async function callbackServer() {
45
75
  export async function login({
46
76
  server, tokenStdin = false, env = process.env, out = console.log, err = console.error, open = openBrowser, fetchImpl = fetch,
47
77
  home, stdin = process.stdin, hostname = os.hostname(), timeoutMs = LOGIN_TIMEOUT_MS,
78
+ prompt = s => process.stderr.write(s),
48
79
  }) {
49
80
  const url = serverOf({ flag: server, env });
50
81
  if (!url) {
@@ -52,7 +83,15 @@ export async function login({
52
83
  return 2;
53
84
  }
54
85
  if (tokenStdin) {
55
- const token = (await readAll(stdin)).trim();
86
+ let token;
87
+ if (stdin.isTTY) {
88
+ prompt(t('login.pasteToken'));
89
+ token = await readHiddenLine(stdin, prompt);
90
+ if (token === null) return 130;
91
+ } else {
92
+ token = await readAll(stdin);
93
+ }
94
+ token = token.trim();
56
95
  if (!token) {
57
96
  err(t('login.noStdinToken'));
58
97
  return 2;
@@ -110,11 +110,8 @@ export async function projectPick({
110
110
  let finalAgents = agents;
111
111
  for (const name of targets) {
112
112
  const { server: url } = sources[name];
113
+ // 토큰이 없어도 공개 서버(PUBLIC_READ)에서는 받을 수 있다. 비공개면 서버의 401 이 로그인 안내가 된다.
113
114
  const token = tokenFor(url, { env, home, single });
114
- if (!token) {
115
- failures.push(t('common.sourceLoginNeeded', { name, url }));
116
- continue;
117
- }
118
115
  const client = remoteClient({ server: url, token, fetchImpl });
119
116
  let src;
120
117
  try {
@@ -149,7 +146,7 @@ export async function projectPick({
149
146
  err(targets.length > 1 ? t('pick.sourceForm', { name, url: form.url }) : t('pick.form', { url: form.url }));
150
147
  if (browser) open(form.url);
151
148
  do {
152
- result = await client.formResult(form.id, pollWait);
149
+ result = await client.formResult(form.id, pollWait, new URL(form.url).searchParams.get('k'));
153
150
  } while (result.status === 'pending');
154
151
  } catch (e) {
155
152
  failures.push(`${name}: ${e.status === 404 ? t('pick.formGone') : explain(e, url)}`);
@@ -67,10 +67,6 @@ export async function source({
67
67
  err(t('common.noServer'));
68
68
  return 2;
69
69
  }
70
- if (!token) {
71
- err(t('common.loginNeeded', { url }));
72
- return 1;
73
- }
74
70
  let src;
75
71
  try {
76
72
  src = await loadSource(remoteClient({ server: url, token, fetchImpl }));
package/cli/src/remote.js CHANGED
@@ -30,8 +30,8 @@ export function serverOf({ flag, manifest, env = process.env }) {
30
30
  */
31
31
  export function remoteClient({ server, token = null, fetchImpl = fetch, version = packageVersion() }) {
32
32
  const base = `${server.replace(/\/+$/, '')}/`;
33
- async function call(method, apiPath, { body, raw = false, auth = true } = {}) {
34
- const headers = { 'X-Guideline-Client': version };
33
+ async function call(method, apiPath, { body, raw = false, auth = true, extra = {} } = {}) {
34
+ const headers = { 'X-Guideline-Client': version, ...extra };
35
35
  if (auth && token) headers.Authorization = `Bearer ${token}`;
36
36
  if (body !== undefined) headers['Content-Type'] = 'application/json';
37
37
  let res;
@@ -59,7 +59,8 @@ export function remoteClient({ server, token = null, fetchImpl = fetch, version
59
59
  tree: rev => call('GET', rev ? `tree?rev=${rev}` : 'tree'),
60
60
  archive: rev => call('GET', `archive?rev=${rev}`, { raw: true }),
61
61
  createForm: body => call('POST', 'forms', { body }),
62
- formResult: (id, wait) => call('GET', `forms/${encodeURIComponent(id)}/result?wait=${wait}`),
62
+ // 토큰 없이 만든 폼은 링크 토큰(폼 주소의 k)이 결과를 볼 권한이다.
63
+ formResult: (id, wait, link = null) => call('GET', `forms/${encodeURIComponent(id)}/result?wait=${wait}`, { extra: link ? { 'X-Form-Link': link } : {} }),
63
64
  exchange: (code, verifier) => call('POST', 'cli/token', { body: { code, verifier }, auth: false }),
64
65
  revokeSelf: () => call('DELETE', 'tokens/self'),
65
66
  createProposal: body => call('POST', 'proposals', { body }),
@@ -55,11 +55,8 @@ export async function fetchSources({
55
55
  const failures = [];
56
56
  for (const name of names) {
57
57
  const server = trim(manifest.sources[name].server);
58
+ // 토큰이 없으면 없이 부른다. 공개 서버(PUBLIC_READ)는 받아 주고, 비공개면 401 이 로그인 안내가 된다.
58
59
  const token = tokenFor(server, { env, home, single });
59
- if (!token) {
60
- failures.push({ source: name, message: t('common.loginNeeded', { url: server }) });
61
- continue;
62
- }
63
60
  const client = remoteClient({ server, token, fetchImpl });
64
61
  const rev = lock?.revisions?.[name];
65
62
  try {
@@ -40,7 +40,6 @@
40
40
  "common.noServer": "No server address: pass --server <URL> or set GUIDELINE_SERVER",
41
41
  "common.notDir": "Not a directory: {dir}",
42
42
  "common.serverWithManySources": "With several sources, use --source instead of --server",
43
- "common.sourceLoginNeeded": "{name}: login required: guideline login --server {url}",
44
43
  "common.tooManyTries": "Too many failed attempts. Try again in {minutes} min",
45
44
  "diff.differs": "{path} differs",
46
45
  "diff.eolOnly": "(only the final newline differs)",
@@ -92,6 +91,7 @@
92
91
  "login.callbackPage": "Login received. You can close this tab.",
93
92
  "login.done": "Logged in: {url} ({name})",
94
93
  "login.loggedOut": "Logged out: {url}",
94
+ "login.pasteToken": "Paste the token and press Enter (it is not shown): ",
95
95
  "login.noStdinToken": "No token on standard input",
96
96
  "login.notLoggedIn": "Not logged in",
97
97
  "login.openUrl": "Log in: {url}",
@@ -6,7 +6,7 @@ This document is the guide agents and people read when they first use this serve
6
6
 
7
7
  ## Setup (once per machine)
8
8
 
9
- 1. You need Node 24 or later and the `guideline` CLI (the `guideline-manage` npm package, 0.15 or later):
9
+ 1. You need Node 24 or later and the `guideline` CLI (the `guideline-manage` npm package, 0.16 or later):
10
10
 
11
11
  ```bash
12
12
  npm install -g guideline-manage
@@ -14,7 +14,7 @@ This document is the guide agents and people read when they first use this serve
14
14
 
15
15
  To use it straight from a clone of its repository, call `node <repo>/cli/bin/guideline.js <command>`.
16
16
 
17
- 2. Log in. When the browser opens, log in with the admin token and click "Issue". A read token is stored in `~/.config/guideline/credentials.json` (mode 600).
17
+ 2. Log in. If the server is public (`PUBLIC_READ=1`, the default), fetching guidelines (pick/update/list) works without a login, so you only need one to send proposals. When the browser opens, log in with the admin token and click "Issue". A read token is stored in `~/.config/guideline/credentials.json` (mode 600).
18
18
 
19
19
  ```bash
20
20
  guideline login --server {{SERVER}}
@@ -127,5 +127,5 @@ guideline propose --from <that folder> --title "<title>" # send what was added
127
127
 
128
128
  ## More
129
129
 
130
- - [Admin web]({{SERVER}}/): view, edit and browse the history of guidelines, review proposals, issue tokens (log in with the admin token)
130
+ - [Web]({{SERVER}}/): anyone can view guidelines and their history (log in if the server is private); editing, reverting, reviewing proposals and issuing tokens need a login with the admin token
131
131
  - `docs/guide.md` in the repository: the user guide for people
@@ -40,7 +40,6 @@
40
40
  "common.noServer": "서버 주소가 없다: --server <URL> 이나 GUIDELINE_SERVER 를 준다",
41
41
  "common.notDir": "디렉터리가 아닙니다: {dir}",
42
42
  "common.serverWithManySources": "출처가 여럿이면 --server 대신 --source 를 쓴다",
43
- "common.sourceLoginNeeded": "{name}: 로그인이 필요하다: guideline login --server {url}",
44
43
  "common.tooManyTries": "너무 많이 틀렸다. {minutes}분 뒤에 다시 한다",
45
44
  "diff.differs": "{path} 다름",
46
45
  "diff.eolOnly": "(파일 끝 줄바꿈만 다름)",
@@ -92,6 +91,7 @@
92
91
  "login.callbackPage": "로그인 정보를 받았다. 이 탭은 닫아도 된다.",
93
92
  "login.done": "로그인했다: {url} ({name})",
94
93
  "login.loggedOut": "로그아웃했다: {url}",
94
+ "login.pasteToken": "토큰을 붙여 넣고 Enter (화면에 보이지 않는다): ",
95
95
  "login.noStdinToken": "표준 입력에 토큰이 없다",
96
96
  "login.notLoggedIn": "로그인돼 있지 않다",
97
97
  "login.openUrl": "로그인: {url}",
@@ -6,7 +6,7 @@
6
6
 
7
7
  ## 준비 (머신마다 한 번)
8
8
 
9
- 1. Node 24 이상과 `guideline` CLI(npm 패키지 `guideline-manage` 0.15 이상)가 있어야 한다.
9
+ 1. Node 24 이상과 `guideline` CLI(npm 패키지 `guideline-manage` 0.16 이상)가 있어야 한다.
10
10
 
11
11
  ```bash
12
12
  npm install -g guideline-manage
@@ -14,7 +14,7 @@
14
14
 
15
15
  저장소 클론에서 바로 쓸 때는 `node <저장소>/cli/bin/guideline.js <명령>` 으로 부른다.
16
16
 
17
- 2. 로그인한다. 브라우저가 열리면 관리자 토큰으로 로그인해 「발급」 을 누른다. 읽기 토큰이 `~/.config/guideline/credentials.json`(권한 600)에 저장된다.
17
+ 2. 로그인한다. 서버가 공개(`PUBLIC_READ=1`, 기본)면 지침 받기(pick·update·list)는 로그인 없이 되니 제안을 올릴 때만 하면 된다. 브라우저가 열리면 관리자 토큰으로 로그인해 「발급」 을 누른다. 읽기 토큰이 `~/.config/guideline/credentials.json`(권한 600)에 저장된다.
18
18
 
19
19
  ```bash
20
20
  guideline login --server {{SERVER}}
@@ -127,5 +127,5 @@ guideline propose --from <그 폴더> --title "<제목>" # 그 폴더에서
127
127
 
128
128
  ## 더 보기
129
129
 
130
- - [관리자 웹]({{SERVER}}/): 지침 보기·편집·이력·제안 검토·토큰 발급(관리자 토큰으로 로그인)
130
+ - [웹]({{SERVER}}/): 지침·이력 보기는 누구나(서버가 비공개면 로그인), 편집·되돌리기·제안 검토·토큰 발급은 관리자 토큰으로 로그인한 뒤
131
131
  - 저장소의 `docs/guide.md`: 사람을 위한 이용 가이드
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guideline-manage",
3
- "version": "0.15.0",
3
+ "version": "0.16.1",
4
4
  "description": "Keep shared coding-agent guidelines on a guideline server and pick them into ruler projects",
5
5
  "keywords": [
6
6
  "ai",