z2o-cli 0.6.0 → 0.8.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 +19 -1
- package/index.js +189 -15
- package/package.json +3 -2
- package/plugin/.claude-plugin/plugin.json +11 -0
- package/plugin/skills/ingest/SKILL.md +95 -0
- package/plugin/skills/read/SKILL.md +45 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm i -g z2o-cli
|
|
7
|
-
z2o init z2o_live_... #
|
|
7
|
+
z2o init z2o_live_... # z2o.kr 프로필 → 「연결 토큰」에서 발급한 열쇠
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
`init` 이 세 가지를 한 번에 한다 — **로그인 · 워크스페이스 지정 · AI 규칙 파일에 사용법 심기.**
|
|
@@ -30,6 +30,19 @@ z2o find 계약 # 원본에서 찾기
|
|
|
30
30
|
Claude Code·Cursor·Codex 등 **셸을 쓸 수 있는 AI라면 무엇에서든** 된다.
|
|
31
31
|
MCP 와 달리 앱마다 연동 설정이 필요 없고, 토큰이 만료되지 않아 한 번 연결해 두면 계속 이어진다.
|
|
32
32
|
|
|
33
|
+
## Claude Code 스킬 — `/z2o:read` · `/z2o:ingest`
|
|
34
|
+
|
|
35
|
+
`init` 은 Claude Code 가 깔려 있으면 **스킬 둘**도 `~/.claude/skills/z2o/` 에 넣는다.
|
|
36
|
+
|
|
37
|
+
| | 언제 |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `/z2o:read` | 회사 맥락을 찾아 답할 때 — 슬랙·회의록·위키 |
|
|
40
|
+
| `/z2o:ingest` | 자료를 위키에 넣고 정리할 때 — 넣기 → 뼈대 → 관련 문서 고쳐 쓰기 |
|
|
41
|
+
|
|
42
|
+
직접 불러도 되고, **그냥 "이거 위키에 정리해"라고 말해도** Claude 가 알아서 꺼내 쓴다.
|
|
43
|
+
평소에는 설명 한 줄(약 90토큰)만 들고 있다가 쓸 때 본문을 읽는다.
|
|
44
|
+
새 판으로 올린 뒤에는 `z2o skills` 로 스킬만 바꿔 끼운다.
|
|
45
|
+
|
|
33
46
|
## 명령
|
|
34
47
|
|
|
35
48
|
| | |
|
|
@@ -47,7 +60,12 @@ MCP 와 달리 앱마다 연동 설정이 필요 없고, 토큰이 만료되지
|
|
|
47
60
|
| `z2o recent [개수]` | **정리 전 원본** — 슬랙·메일이 들어온 그대로 |
|
|
48
61
|
| `z2o find <낱말>` | 원본에서 찾기 |
|
|
49
62
|
| `z2o raw <id...>` | 원본 본문 |
|
|
63
|
+
| `z2o rules` | 이 워크스페이스의 위키 규칙 — 쓰기 전에 먼저 |
|
|
64
|
+
| `z2o skills` | Claude Code 스킬 설치·갱신 |
|
|
50
65
|
| `z2o push <제목> <파일\|-\>` | 자료 넣기 — **쓰기 토큰 필요** |
|
|
66
|
+
| `z2o build` | 원문·장부·표 만들기 (코드만, 0원) |
|
|
67
|
+
| `z2o pending` | 요약이 필요한 원본 |
|
|
68
|
+
| `z2o write <경로> <파일\|-\>` | 요약을 위키에 남기기 — **쓰기 토큰 필요** |
|
|
51
69
|
| `z2o organize` | 정리 돌리기 — **쓰기 토큰 필요** |
|
|
52
70
|
|
|
53
71
|
`-c <id>` 로 워크스페이스를 그때만 바꿀 수 있다.
|
package/index.js
CHANGED
|
@@ -14,9 +14,14 @@
|
|
|
14
14
|
* 이쪽 설치가 무거우면 만든 이유가 없어진다.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync } from 'node:fs';
|
|
18
|
-
import { homedir } from 'node:os';
|
|
19
|
-
import { join } from 'node:path';
|
|
17
|
+
import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync, cpSync, rmSync } from 'node:fs';
|
|
18
|
+
import { homedir, hostname } from 'node:os';
|
|
19
|
+
import { join, dirname } from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
import { createServer } from 'node:http';
|
|
22
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
23
|
+
import { spawn } from 'node:child_process';
|
|
24
|
+
import { createInterface } from 'node:readline/promises';
|
|
20
25
|
|
|
21
26
|
const DEFAULT_API = 'https://z2o.kr/_/backend';
|
|
22
27
|
// 발급되는 토큰의 모양. `z2o_live_` + base64url.
|
|
@@ -80,7 +85,7 @@ async function rpc(method, params, { token: override } = {}) {
|
|
|
80
85
|
// `login` 은 **아직 저장하지 않은** 토큰으로 확인해야 한다 — 그래서 넘겨받는 길을 둔다.
|
|
81
86
|
const token = override || saved;
|
|
82
87
|
if (!token) {
|
|
83
|
-
die('
|
|
88
|
+
die('아직 연결 안 됐어요.\n `z2o login` 을 하면 브라우저에서 「허용」 한 번으로 연결됩니다.');
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
if (token && !TOKEN_RE.test(token)) {
|
|
@@ -101,7 +106,7 @@ async function rpc(method, params, { token: override } = {}) {
|
|
|
101
106
|
die(`서버에 닿지 못했어요 (${api}).\n ${e.message}`);
|
|
102
107
|
}
|
|
103
108
|
|
|
104
|
-
if (res.status === 401) die('토큰이
|
|
109
|
+
if (res.status === 401) die('연결이 끊겼어요(토큰이 끊겼거나 틀림). `z2o login` 으로 다시 연결해 주세요.');
|
|
105
110
|
if (res.status === 403) {
|
|
106
111
|
// 읽기 전용 토큰으로 쓰기를 시도한 경우. 서버 문구를 그대로 전한다.
|
|
107
112
|
let detail = '';
|
|
@@ -130,6 +135,119 @@ async function callTool(name, args, opts) {
|
|
|
130
135
|
return text;
|
|
131
136
|
}
|
|
132
137
|
|
|
138
|
+
// ── 브라우저로 붙이기 ────────────────────────────────────
|
|
139
|
+
//
|
|
140
|
+
// 🔑 **사람이 토큰 문자열을 볼 일이 없게 한다.** 터미널에 붙여 넣는 것은 스푸핑·탈취
|
|
141
|
+
// 위험이 있고(2026-09-10 데일리), 비개발자에게는 그 자체가 벽이다. `gh auth login` 과
|
|
142
|
+
// 같은 모양 — 브라우저에서 「허용」을 누르면 여기로 돌아온다.
|
|
143
|
+
//
|
|
144
|
+
// 🔑 **PKCE.** 비밀값은 이 프로세스 밖으로 안 나간다. 브라우저를 오가는 것은 그 해시와
|
|
145
|
+
// 5분짜리 코드뿐이고, 코드는 비밀값 없이는 토큰으로 못 바꾼다.
|
|
146
|
+
|
|
147
|
+
const b64url = (buf) => buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
148
|
+
|
|
149
|
+
/** 백엔드 주소에서 웹 주소를 얻는다. 운영은 같은 도메인 뒤에 `/_/backend` 가 붙는다. */
|
|
150
|
+
function webBase(api) {
|
|
151
|
+
if (process.env.Z2O_WEB) return process.env.Z2O_WEB.replace(/\/+$/, '');
|
|
152
|
+
if (/^https?:\/\/(localhost|127\.0\.0\.1):8000$/.test(api)) return api.replace(':8000', ':3000');
|
|
153
|
+
return api.replace(/\/_\/backend$/, '');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function openBrowser(url) {
|
|
157
|
+
const [cmd, args] =
|
|
158
|
+
process.platform === 'darwin' ? ['open', [url]]
|
|
159
|
+
: process.platform === 'win32' ? ['cmd', ['/c', 'start', '""', url]]
|
|
160
|
+
: ['xdg-open', [url]];
|
|
161
|
+
try {
|
|
162
|
+
const p = spawn(cmd, args, { stdio: 'ignore', detached: true });
|
|
163
|
+
p.on('error', () => { /* 못 열어도 주소를 찍어 두었으니 사람이 연다 */ });
|
|
164
|
+
p.unref();
|
|
165
|
+
return true;
|
|
166
|
+
} catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const DONE_HTML = (msg) => `<!doctype html><meta charset="utf-8"><title>제로투원</title>
|
|
172
|
+
<body style="font-family:-apple-system,system-ui,sans-serif;background:#f6f6f2;display:grid;place-items:center;height:100vh;margin:0">
|
|
173
|
+
<div style="text-align:center;color:#34322e"><div style="font-size:18px;font-weight:600">${msg}</div>
|
|
174
|
+
<div style="margin-top:8px;font-size:13px;opacity:.55">이 창은 닫아도 돼요.</div></div></body>`;
|
|
175
|
+
|
|
176
|
+
/** 브라우저에서 허용받아 토큰을 저장한다. 워크스페이스를 매었으면 그것도 정해 둔다. */
|
|
177
|
+
async function browserLogin({ paste = false } = {}) {
|
|
178
|
+
const { api } = settings();
|
|
179
|
+
const web = webBase(api);
|
|
180
|
+
const verifier = b64url(randomBytes(32));
|
|
181
|
+
const challenge = b64url(createHash('sha256').update(verifier).digest());
|
|
182
|
+
const state = b64url(randomBytes(16));
|
|
183
|
+
const q = new URLSearchParams({ cc: challenge, state, name: `${hostname().replace(/\.local$/, '')} · z2o` });
|
|
184
|
+
|
|
185
|
+
let code;
|
|
186
|
+
if (paste) {
|
|
187
|
+
// 브라우저가 다른 기기에 있다(원격 서버·SSH). 여기로 돌아올 길이 없으니 코드를 옮겨 적는다.
|
|
188
|
+
q.set('mode', 'paste');
|
|
189
|
+
console.log(`이 주소를 브라우저에서 열고 「허용」을 누르세요:\n\n ${web}/cli-login?${q}\n`);
|
|
190
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
191
|
+
code = (await rl.question('화면에 뜬 연결 코드를 붙여 넣으세요: ')).trim();
|
|
192
|
+
rl.close();
|
|
193
|
+
} else {
|
|
194
|
+
code = await new Promise((resolve, reject) => {
|
|
195
|
+
// 🔑 **내 컴퓨터에서만 받는다**(127.0.0.1). 포트는 비어 있는 아무 곳 — 고정하면 겹친다.
|
|
196
|
+
const server = createServer((req, res) => {
|
|
197
|
+
const u = new URL(req.url || '/', 'http://127.0.0.1');
|
|
198
|
+
if (u.pathname !== '/callback') {
|
|
199
|
+
res.writeHead(404).end();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
// ⚠️ `state` 가 다르면 이 창에서 시작한 요청이 아니다 — 받지 않는다.
|
|
203
|
+
const got = u.searchParams.get('state') === state ? u.searchParams.get('code') : null;
|
|
204
|
+
res.writeHead(got ? 200 : 400, { 'Content-Type': 'text/html; charset=utf-8', Connection: 'close' });
|
|
205
|
+
res.end(DONE_HTML(got ? '연결됐어요. 터미널로 돌아가세요.' : '요청이 맞지 않아요. 터미널에서 z2o login 을 다시 해 주세요.'));
|
|
206
|
+
if (!got) return;
|
|
207
|
+
clearTimeout(timer);
|
|
208
|
+
server.close();
|
|
209
|
+
resolve(got);
|
|
210
|
+
});
|
|
211
|
+
const timer = setTimeout(() => {
|
|
212
|
+
server.close();
|
|
213
|
+
reject(new Error('5분 안에 허용하지 않아 멈췄어요. `z2o login` 을 다시 해 주세요.'));
|
|
214
|
+
}, 5 * 60 * 1000);
|
|
215
|
+
server.on('error', reject);
|
|
216
|
+
server.listen(0, '127.0.0.1', () => {
|
|
217
|
+
q.set('port', String(server.address().port));
|
|
218
|
+
const url = `${web}/cli-login?${q}`;
|
|
219
|
+
console.log(openBrowser(url)
|
|
220
|
+
? '브라우저에서 제로투원을 열었어요. 「허용」을 누르면 여기로 돌아옵니다.'
|
|
221
|
+
: '브라우저를 열지 못했어요.');
|
|
222
|
+
console.log(`\n안 열리면 이 주소를 직접 여세요:\n ${url}\n`);
|
|
223
|
+
console.log('(브라우저가 다른 기기에 있으면 Ctrl+C 로 멈추고 `z2o login --paste`)\n');
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (!code) die('연결 코드가 비었어요.');
|
|
228
|
+
|
|
229
|
+
let res;
|
|
230
|
+
try {
|
|
231
|
+
res = await fetch(`${api}/api/tokens/cli/exchange`, {
|
|
232
|
+
method: 'POST',
|
|
233
|
+
headers: { 'Content-Type': 'application/json' },
|
|
234
|
+
body: JSON.stringify({ code, code_verifier: verifier }),
|
|
235
|
+
});
|
|
236
|
+
} catch (e) {
|
|
237
|
+
die(`서버에 닿지 못했어요 (${api}).\n ${e.message}`);
|
|
238
|
+
}
|
|
239
|
+
const body = await res.json().catch(() => ({}));
|
|
240
|
+
if (!res.ok || !body.token) die(body.detail || `서버가 ${res.status} 로 답했어요.`);
|
|
241
|
+
|
|
242
|
+
// 워크스페이스에 매인 토큰이면 그걸 기본으로 — `z2o use` 를 따로 안 해도 된다.
|
|
243
|
+
saveConf({ ...loadConf(), token: body.token, ...(body.company_id ? { companyId: body.company_id } : {}) });
|
|
244
|
+
console.log(
|
|
245
|
+
`연결했어요 — ${body.company_name || '내가 볼 수 있는 워크스페이스 전부'} · ` +
|
|
246
|
+
`${body.scope === 'write' ? '자료·정리까지' : '읽기만'}`,
|
|
247
|
+
);
|
|
248
|
+
return body;
|
|
249
|
+
}
|
|
250
|
+
|
|
133
251
|
/** 회사 id 를 정한다. `-c` > 환경변수 > 설정 파일 순. */
|
|
134
252
|
function needCompany(flags) {
|
|
135
253
|
const id = flags.c || settings().companyId;
|
|
@@ -147,6 +265,36 @@ function needCompany(flags) {
|
|
|
147
265
|
|
|
148
266
|
// 🌍 **도구마다 「모든 폴더에 적용되는」 규칙 파일 자리가 따로 있다.**
|
|
149
267
|
// 여기 한 번 심으면 폴더마다 `init` 을 돌릴 필요가 없다.
|
|
268
|
+
// 🧩 **Claude Code 에는 스킬로도 심는다** — `/z2o:read` · `/z2o:ingest`.
|
|
269
|
+
// 규칙 파일 블록은 **모든 대화에 항상** 실리지만, 스킬은 한 줄 설명(~90토큰)만 늘 들고
|
|
270
|
+
// 본문은 **부를 때만** 읽힌다. 그래서 절차를 길고 정확하게 적을 수 있다.
|
|
271
|
+
// `~/.claude/skills/<이름>/` 에 두면 마켓플레이스 없이 다음 세션부터 `z2o@skills-dir` 로 뜬다.
|
|
272
|
+
// ⚠️ 규격은 서버(`z2o rules`), **순서는 스킬** — 회사마다 다른 것을 패키지에 박지 않는다.
|
|
273
|
+
const PLUGIN_SRC = join(dirname(fileURLToPath(import.meta.url)), 'plugin');
|
|
274
|
+
|
|
275
|
+
function installPlugin() {
|
|
276
|
+
const home = join(homedir(), '.claude');
|
|
277
|
+
if (!existsSync(home) || !existsSync(PLUGIN_SRC)) return null;
|
|
278
|
+
const dest = join(home, 'skills', 'z2o');
|
|
279
|
+
const mine = join(dest, '.claude-plugin', 'plugin.json');
|
|
280
|
+
if (existsSync(dest)) {
|
|
281
|
+
// 같은 이름의 **남의 것**은 덮지 않는다.
|
|
282
|
+
let ours = false;
|
|
283
|
+
try { ours = JSON.parse(readFileSync(mine, 'utf8')).name === 'z2o'; } catch { /* 없음 */ }
|
|
284
|
+
if (!ours) return { dest, state: '건너뜀 — 같은 이름의 다른 폴더가 있어요' };
|
|
285
|
+
rmSync(dest, { recursive: true, force: true }); // 옛 판의 지운 스킬이 남지 않게
|
|
286
|
+
}
|
|
287
|
+
mkdirSync(join(home, 'skills'), { recursive: true });
|
|
288
|
+
cpSync(PLUGIN_SRC, dest, { recursive: true });
|
|
289
|
+
return { dest, state: '설치' };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function reportPlugin(r = installPlugin()) {
|
|
293
|
+
if (!r) return;
|
|
294
|
+
console.log(`~/.claude/skills/z2o — ${r.state}`);
|
|
295
|
+
if (r.state === '설치') console.log(' Claude Code 스킬 /z2o:read · /z2o:ingest (다음 세션부터, 지금은 /reload-plugins)');
|
|
296
|
+
}
|
|
297
|
+
|
|
150
298
|
const GLOBAL_RULES = [
|
|
151
299
|
['.claude', 'CLAUDE.md'],
|
|
152
300
|
['.codex', 'AGENTS.md'],
|
|
@@ -176,6 +324,7 @@ const BLOCK = `${MARK_START}
|
|
|
176
324
|
축 구성·문서 형식·건드리지 않을 곳·정리 순서가 거기 있다. **회사마다 다르고
|
|
177
325
|
사용자가 고쳐 두었을 수 있으므로, 이 블록의 기억에 의존하지 않는다.**
|
|
178
326
|
|
|
327
|
+
Claude Code 에서는 스킬 \`/z2o:read\` · \`/z2o:ingest\` 가 순서를 담고 있다.
|
|
179
328
|
자세한 명령은 \`z2o --help\`.
|
|
180
329
|
${MARK_END}`;
|
|
181
330
|
|
|
@@ -213,6 +362,7 @@ function parseArgs(argv) {
|
|
|
213
362
|
const a = argv[i];
|
|
214
363
|
if (a === '-c' || a === '--company') flags.c = argv[++i];
|
|
215
364
|
else if (a === '-g' || a === '--global') flags.global = true;
|
|
365
|
+
else if (a === '--paste') flags.paste = true;
|
|
216
366
|
else if (a === '-h' || a === '--help') flags.help = true;
|
|
217
367
|
else rest.push(a);
|
|
218
368
|
}
|
|
@@ -221,9 +371,11 @@ function parseArgs(argv) {
|
|
|
221
371
|
|
|
222
372
|
const HELP = `z2o — 회사 온톨로지를 내가 쓰는 AI에서 꺼내 쓴다
|
|
223
373
|
|
|
224
|
-
z2o init
|
|
374
|
+
z2o init 한 번에 붙이기 — 브라우저 로그인 + 워크스페이스 + AI 규칙파일
|
|
225
375
|
z2o init -g 규칙을 **모든 폴더**에 (~/.claude 등). 폴더마다 안 해도 됨
|
|
226
|
-
z2o login
|
|
376
|
+
z2o login 브라우저에서 「허용」 한 번으로 연결 (토큰을 볼 일이 없다)
|
|
377
|
+
z2o login --paste 브라우저가 다른 기기에 있을 때 — 화면의 코드를 붙여 넣는다
|
|
378
|
+
z2o login <토큰> 발급받은 토큰을 직접 넣기
|
|
227
379
|
z2o logout 저장한 토큰 지우기
|
|
228
380
|
z2o whoami 지금 어떻게 붙어 있나
|
|
229
381
|
|
|
@@ -243,6 +395,7 @@ const HELP = `z2o — 회사 온톨로지를 내가 쓰는 AI에서 꺼내 쓴
|
|
|
243
395
|
|
|
244
396
|
── 정리를 내 AI 가 한다 ── (쓰기 토큰 필요)
|
|
245
397
|
z2o rules 이 워크스페이스의 위키 규칙 — **쓰기 전에 먼저**
|
|
398
|
+
z2o skills Claude Code 스킬 설치·갱신 (/z2o:read · /z2o:ingest)
|
|
246
399
|
z2o push <제목> <파일|-> 원본 넣기
|
|
247
400
|
z2o build 원문·장부·표 만들기 (코드만, 0원)
|
|
248
401
|
z2o pending 요약이 필요한 원본
|
|
@@ -251,14 +404,23 @@ const HELP = `z2o — 회사 온톨로지를 내가 쓰는 AI에서 꺼내 쓴
|
|
|
251
404
|
|
|
252
405
|
-c, --company <id> 이번만 다른 워크스페이스로
|
|
253
406
|
-g, --global init 을 모든 폴더에 적용
|
|
407
|
+
--paste login·init 을 코드 붙여넣기로
|
|
254
408
|
|
|
255
409
|
환경 변수: Z2O_API · Z2O_TOKEN · Z2O_COMPANY (설정 파일보다 우선)`;
|
|
256
410
|
|
|
257
411
|
// ── 명령 ────────────────────────────────────────────────
|
|
258
412
|
|
|
259
413
|
const COMMANDS = {
|
|
260
|
-
async login([token]) {
|
|
261
|
-
|
|
414
|
+
async login([token], flags = {}) {
|
|
415
|
+
// 토큰을 안 주면 브라우저로 — 이게 기본 길이다. 토큰을 직접 넣는 길은 남겨 둔다(CI·스크립트).
|
|
416
|
+
if (!token) {
|
|
417
|
+
await browserLogin({ paste: flags.paste });
|
|
418
|
+
if (!settings().companyId) {
|
|
419
|
+
console.log('\n' + (await callTool('list_companies', {})));
|
|
420
|
+
console.log('\n`z2o use <id 또는 이름>` 으로 기본 워크스페이스를 정해 두세요.');
|
|
421
|
+
}
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
262
424
|
if (!TOKEN_RE.test(token)) {
|
|
263
425
|
die('토큰 모양이 아니에요. `z2o_live_` 로 시작하는 한 줄이어야 합니다.');
|
|
264
426
|
}
|
|
@@ -281,15 +443,12 @@ const COMMANDS = {
|
|
|
281
443
|
const [token] = args;
|
|
282
444
|
const { token: saved } = settings();
|
|
283
445
|
|
|
284
|
-
// ① 로그인 — 토큰을 줬으면 그걸로, 이미 붙어 있으면 그대로.
|
|
446
|
+
// ① 로그인 — 토큰을 줬으면 그걸로, 없으면 브라우저로, 이미 붙어 있으면 그대로.
|
|
285
447
|
if (token) {
|
|
286
448
|
await COMMANDS.login([token]);
|
|
287
449
|
} else if (!saved) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
' 1. https://z2o.kr 로그인 → 오른쪽 위 프로필 → 「연결 토큰」 → 발급\n' +
|
|
291
|
-
' 2. z2o init <발급받은 열쇠>',
|
|
292
|
-
);
|
|
450
|
+
await browserLogin({ paste: flags.paste });
|
|
451
|
+
console.log('');
|
|
293
452
|
} else {
|
|
294
453
|
console.log('이미 연결돼 있어요.\n');
|
|
295
454
|
}
|
|
@@ -328,6 +487,7 @@ const COMMANDS = {
|
|
|
328
487
|
console.log(`~/${d}/${f} — ${r.state}`);
|
|
329
488
|
}
|
|
330
489
|
console.log(' (모든 폴더에서 통합니다)');
|
|
490
|
+
reportPlugin();
|
|
331
491
|
console.log('\n이제 AI 에게 그냥 물어보세요:');
|
|
332
492
|
console.log(' "최근 슬랙에서 나온 얘기 정리해줘"');
|
|
333
493
|
if (needPick) {
|
|
@@ -351,6 +511,7 @@ const COMMANDS = {
|
|
|
351
511
|
console.log(
|
|
352
512
|
' (다른 도구를 쓰면 그 도구의 규칙 파일을 만들어 두고 `z2o init` 을 다시 돌리세요)',
|
|
353
513
|
);
|
|
514
|
+
reportPlugin();
|
|
354
515
|
|
|
355
516
|
// ⚠️ **다 된 것처럼 끝내지 않는다.** 워크스페이스를 못 정했으면 아직 아무것도 안 된다 —
|
|
356
517
|
// 여기서 "이제 물어보세요" 라고 하면 사람은 됐다고 믿고 넘어간다.
|
|
@@ -365,6 +526,13 @@ const COMMANDS = {
|
|
|
365
526
|
void flags;
|
|
366
527
|
},
|
|
367
528
|
|
|
529
|
+
// 스킬만 깔거나 새 판으로 바꾼다 — 토큰이 없어도 된다. `npm i -g z2o-cli` 로 올린 뒤에 한 번.
|
|
530
|
+
async skills() {
|
|
531
|
+
const r = installPlugin();
|
|
532
|
+
if (!r) die('Claude Code 가 안 보여요(~/.claude 없음). Claude Code 를 한 번 실행한 뒤 다시 해 주세요.');
|
|
533
|
+
reportPlugin(r);
|
|
534
|
+
},
|
|
535
|
+
|
|
368
536
|
async logout() {
|
|
369
537
|
const conf = loadConf();
|
|
370
538
|
delete conf.token;
|
|
@@ -523,6 +691,12 @@ const COMMANDS = {
|
|
|
523
691
|
|
|
524
692
|
// ── 진입점 ──────────────────────────────────────────────
|
|
525
693
|
|
|
694
|
+
if (['--version', '-v', 'version'].includes(process.argv[2])) {
|
|
695
|
+
const pkg = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), 'package.json'), 'utf8'));
|
|
696
|
+
console.log(pkg.version);
|
|
697
|
+
process.exit(0);
|
|
698
|
+
}
|
|
699
|
+
|
|
526
700
|
const { flags, rest } = parseArgs(process.argv.slice(2));
|
|
527
701
|
const [cmd, ...args] = rest;
|
|
528
702
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "z2o-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "제로투원 — 회사 온톨로지와 슬랙 맥락을 내가 쓰는 AI에서 꺼내 쓴다",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"z2o",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"index.js",
|
|
27
|
-
"README.md"
|
|
27
|
+
"README.md",
|
|
28
|
+
"plugin/"
|
|
28
29
|
],
|
|
29
30
|
"engines": {
|
|
30
31
|
"node": ">=18"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
|
+
"name": "z2o",
|
|
4
|
+
"version": "0.8.0",
|
|
5
|
+
"description": "제로투원 — 회사 위키·슬랙·회의록을 읽고(/z2o:read) 위키에 정리해 넣는다(/z2o:ingest). z2o CLI 를 감싼다.",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Lindy Operation"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://z2o.kr",
|
|
10
|
+
"keywords": ["z2o", "zerotoone", "wiki", "ontology", "slack"]
|
|
11
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ingest
|
|
3
|
+
description: 자료를 제로투원 위키에 넣고 정리할 때 쓴다 — "이거 위키에 올려", "회의록 z2o 에 정리해", "pending 정리해", "온톨로지 정리해", "슬랙 들어온 거 요약해 둬". 원본을 넣고, 관련 문서를 찾아 읽고, 고쳐 쓴 요약을 남긴다. Z2O 의 AI(organize)에 맡기지 않고 내가 한다.
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Bash(z2o *)
|
|
6
|
+
- Bash(mktemp *)
|
|
7
|
+
- Read
|
|
8
|
+
- Write
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# /z2o:ingest — 위키에 정리해 넣기
|
|
12
|
+
|
|
13
|
+
**정리는 내가 한다.** `z2o organize`(Z2O 의 AI)는 사람이 그렇게 해 달라고 할 때만 부른다.
|
|
14
|
+
|
|
15
|
+
요청: `$ARGUMENTS`
|
|
16
|
+
|
|
17
|
+
## 0. 규칙부터 — 매번
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
z2o rules
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
축 구성·문서 형식·건드리지 않을 곳이 거기 있다. **회사마다 다르고 사람이 고쳐 두었을 수 있다.**
|
|
24
|
+
이 스킬은 **순서**를, `z2o rules` 는 **규격**을 맡는다. 둘이 어긋나면 **`z2o rules` 를 따른다.**
|
|
25
|
+
|
|
26
|
+
## 1. 넣기 — 넣을 원본이 있을 때만
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
z2o find <제목의 핵심 낱말> 이미 들어와 있나 먼저 본다
|
|
30
|
+
z2o push "<제목>" <파일> 없을 때만
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- ⚠️ **슬랙·티로·캘린더는 매시 자동 수집된다.** 회의록을 손으로 또 넣으면 **같은 회의가 두 벌**이 된다.
|
|
34
|
+
`find` 로 찾아지면 넣지 않는다. 방금 끝난 회의면 한 시간 뒤 다시 본다.
|
|
35
|
+
- 원본은 **한 글자도 고치지 않고** 넣는다. 요약은 넣지 않는다 — 요약은 5단계에서 위키에 쓴다.
|
|
36
|
+
- 비밀번호·토큰·계좌번호가 보이면 **넣기 전에 사람에게 묻는다.**
|
|
37
|
+
|
|
38
|
+
## 2. 뼈대 — 한 번
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
z2o build
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
원문(`docs/`)·장부·표를 **코드가** 만든다(0원). 이게 없으면 요약에 건 링크가 깨진다.
|
|
45
|
+
**두 번 부르지 않는다.**
|
|
46
|
+
|
|
47
|
+
## 3. 할 일 보기
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
z2o pending 요약이 필요한 원본
|
|
51
|
+
z2o raw <id> <id> ... 한 번에 여러 개 읽는다
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
잡담뿐인 원본도 있다. **남길 사실만** 추린다 — 누가·무엇을·언제·숫자.
|
|
55
|
+
|
|
56
|
+
## 4. 붙일 자리 찾기 — 건너뛰지 않는다
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
z2o list topics/
|
|
60
|
+
z2o search <낱말>
|
|
61
|
+
z2o read topics/<후보>.md
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**새 문서는 맞는 주제가 정말 없을 때만** 만든다. 새 문서만 늘리면 위키가 누적되지 않고 흩어진다.
|
|
65
|
+
원문 문서의 경로는 `z2o list docs/` 로 확인한다 — **2단계가 만든 경로를 그대로** 쓴다.
|
|
66
|
+
|
|
67
|
+
## 5. 고쳐 쓰기
|
|
68
|
+
|
|
69
|
+
주제 문서는 두 층이다(자세한 모양은 `z2o rules`).
|
|
70
|
+
|
|
71
|
+
- **산문** — 새 자료를 반영해 **다시 쓴다.** 이어 붙이지 말고 한 덩어리로.
|
|
72
|
+
- **`## 문서` 줄** — 기존 줄은 **한 글자도 지우거나 고치지 않는다.** 새 원본 줄만 덧붙인다:
|
|
73
|
+
`- [[docs/<경로>|<제목>]] — 한 줄 설명`
|
|
74
|
+
|
|
75
|
+
파일은 임시로 쓰고 올린다.
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
z2o write topics/<주제>.md <임시파일>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- 문서 머리(`type`·`tags`·`updated`)가 없으면 서버가 틀을 보여 주며 거절한다 — 그 틀대로 고친다.
|
|
82
|
+
- **쓰면 안 되는 곳**: `ledger/` `entities/` `tables/` `docs/` `calendar/` `meetings/` `index.md` `log.md` `overview.md` — 코드가 만든다. 서버도 거절한다.
|
|
83
|
+
|
|
84
|
+
## 6. 확인
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
z2o pending
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
요약이 원문 문서를 링크하면 그 원본은 **pending 에서 빠진다.** 안 빠졌으면 **링크 경로가 틀린 것**이다 —
|
|
91
|
+
`z2o list docs/` 와 대조해 고친다.
|
|
92
|
+
|
|
93
|
+
## 보고
|
|
94
|
+
|
|
95
|
+
사람에게 짧게: 넣은 원본 · 고친/새로 만든 문서 · 남은 pending 수 · **원본끼리 어긋난 것**(날짜·숫자·사람).
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: read
|
|
3
|
+
description: 회사(제로투원) 맥락이 필요할 때 쓴다 — 슬랙·회의록·메일·위키에서 찾아 답한다. "슬랙에서 뭐라고 했지", "지난 회의에서 정한 거", "우리 회사 ~ 어떻게 됐어", "z2o 에서 찾아줘", "최근 얘기 정리해줘" 같은 요청. 사람에게 복사해 달라고 하지 않고 z2o 로 직접 꺼낸다.
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Bash(z2o *)
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# /z2o:read — 회사 맥락 꺼내기
|
|
9
|
+
|
|
10
|
+
회사의 슬랙·회의록·메일·정리된 위키는 전부 `z2o` 명령으로 꺼낸다.
|
|
11
|
+
**사람에게 "복사해서 붙여 주세요"라고 하지 않는다.** 이 스킬은 읽기만 한다 — 쓰기는 `/z2o:ingest`.
|
|
12
|
+
|
|
13
|
+
요청: `$ARGUMENTS`
|
|
14
|
+
|
|
15
|
+
## 어디서 찾나 — 둘로 나뉜다
|
|
16
|
+
|
|
17
|
+
| | 무엇 | 명령 |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| **원본** | 들어온 그대로. 슬랙·메일·티로. **가장 최신** | `z2o recent [개수]` · `z2o find <낱말>` · `z2o raw <id...>` |
|
|
20
|
+
| **위키** | 정리된 지식. 주제·개체·장부 | `z2o list [접두어]` · `z2o search <낱말>` · `z2o read <경로...>` · `z2o graph` |
|
|
21
|
+
|
|
22
|
+
- **오늘·이번 주·"슬랙에서"** 같은 요청 → 원본부터. `recent` 로 훑고 `raw` 로 읽는다.
|
|
23
|
+
- **"~가 뭐였지", "~ 현황"** 같은 요청 → 위키부터.
|
|
24
|
+
- 둘 다 본 뒤 **어긋나면 둘 다 말한다.** 원본이 더 최신이다 — 위키는 정리된 시점에 멈춰 있다.
|
|
25
|
+
|
|
26
|
+
## 위키 찾는 법
|
|
27
|
+
|
|
28
|
+
1. `z2o read index.md overview.md` — 목차와 종합. 어디를 볼지 여기서 정한다.
|
|
29
|
+
2. `z2o list topics/` 처럼 **접두어로 목록**을 본다. 위키가 100장 안팎이면
|
|
30
|
+
**목록을 보고 관련 문서를 통째로 읽는 편이 검색보다 낫다.**
|
|
31
|
+
3. `z2o search` 는 **낱말을 그대로 포함한 것만** 찾는다(최대 30건, 뜻으로 찾지 않는다).
|
|
32
|
+
동의어·다른 표기로 **여러 번** 찾는다. 예: `타임원`·`TimeOne`·`외주`
|
|
33
|
+
4. `z2o read a.md b.md c.md` — **여러 장을 한 번에**(한 번에 40장·12만 자까지).
|
|
34
|
+
5. 주제 문서의 `## 문서` 줄은 원문(`docs/`)을 가리킨다. 산문이 모호하면 원문을 읽는다.
|
|
35
|
+
|
|
36
|
+
## 답할 때
|
|
37
|
+
|
|
38
|
+
- **출처를 붙인다** — 문서 경로(`topics/물류.md`)나 원본 id(`d213b634`).
|
|
39
|
+
- 못 찾았으면 **못 찾았다고 한다.** 짐작으로 메우지 않는다.
|
|
40
|
+
- 워크스페이스가 여럿이면 `-c <id>` 로 고른다. 모르겠으면 `z2o companies`.
|
|
41
|
+
|
|
42
|
+
## 막히면
|
|
43
|
+
|
|
44
|
+
- `토큰이 없어요` → 사람에게 `z2o init` 을 안내한다. **토큰을 대화로 받지 않는다** — 사람이 직접 넣는다.
|
|
45
|
+
- `워크스페이스를 정하세요` → `z2o companies` 로 보여 주고 사람이 `z2o use` 로 고르게 한다.
|