deel-local-cli 1.20.12 → 2.0.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.ko.md +96 -77
- package/README.md +119 -80
- package/bin/deel.js +398 -32
- package/package.json +2 -1
- package/src/acp/jsonrpc.js +87 -7
- package/src/acp/map.js +62 -13
- package/src/acp/serve.js +338 -30
- package/src/agent/agents.js +110 -12
- package/src/agent/askcheck.js +50 -6
- package/src/agent/asks.js +59 -5
- package/src/agent/budget.js +9 -2
- package/src/agent/card.js +8 -2
- package/src/agent/commit.js +168 -16
- package/src/agent/compact.js +93 -21
- package/src/agent/{/354/213/240/353/242/260/353/217/204.js → confidence.js} +1 -1
- package/src/agent/effort.js +85 -15
- package/src/agent/evidence.js +63 -7
- package/src/agent/evolve.js +132 -24
- package/src/agent/grade.js +12 -3
- package/src/agent/loop.js +408 -81
- package/src/agent/memory.js +176 -29
- package/src/agent/mention.js +22 -5
- package/src/agent/models.js +31 -7
- package/src/agent/modes.js +25 -10
- package/src/agent/outschema.js +381 -56
- package/src/agent/pins.js +33 -2
- package/src/agent/project.js +73 -8
- package/src/agent/recall.js +40 -9
- package/src/agent/{/354/204/261/355/225/234/352/270/260/353/241/235.js → recordshape.js} +19 -8
- package/src/agent/review.js +143 -13
- package/src/agent/route.js +506 -32
- package/src/agent/salvage.js +2 -1
- package/src/agent/session.js +457 -31
- package/src/agent/store.js +232 -22
- package/src/agent/threads.js +52 -3
- package/src/backend/adapter.js +508 -85
- package/src/backend/azure.js +12 -1
- package/src/backend/cachemark.js +31 -5
- package/src/backend/clientcert.js +23 -1
- package/src/backend/ctxsize.js +56 -9
- package/src/backend/detect.js +252 -19
- package/src/backend/http.js +383 -42
- package/src/backend/learn.js +101 -11
- package/src/backend/mcp.js +333 -24
- package/src/backend/price.js +61 -13
- package/src/backend/probe.js +188 -36
- package/src/backend/proxy.js +108 -14
- package/src/backend/quota.js +226 -30
- package/src/backend/retry.js +46 -3
- package/src/backend/scan.js +47 -10
- package/src/backend/scanui.js +57 -7
- package/src/backend/toolfit.js +204 -22
- package/src/backend/vision.js +14 -1
- package/src/backend/wire.js +58 -10
- package/src/cmdnames.js +16 -0
- package/src/commands/common.js +70 -0
- package/src/commands/extend.js +300 -0
- package/src/commands/model.js +852 -0
- package/src/commands/view.js +328 -0
- package/src/commands/work.js +823 -0
- package/src/commands.js +193 -2082
- package/src/completion.js +45 -3
- package/src/config.js +452 -39
- package/src/configexplain.js +125 -14
- package/src/doctor.js +164 -26
- package/src/i18n/en.js +22 -0
- package/src/i18n/index.js +31 -1
- package/src/i18n/ja.js +25 -1
- package/src/i18n/ko.js +25 -2
- package/src/i18n/zh.js +25 -1
- package/src/lsp/client.js +111 -9
- package/src/lsp/servers.js +86 -22
- package/src/oneshot.js +325 -65
- package/src/pack/sbom.js +28 -5
- package/src/pack/selfpack.js +54 -5
- package/src/pack/sheet.en.js +24 -3
- package/src/pack/tar.js +45 -8
- package/src/pack/zip.js +103 -8
- package/src/plugins/manage.js +165 -14
- package/src/preview/serve.js +239 -23
- package/src/providers/bedrock.js +30 -5
- package/src/providers/gemini.js +8 -0
- package/src/providers/index.js +17 -1
- package/src/repl.js +254 -61
- package/src/report.js +7 -2
- package/src/reset.js +151 -25
- package/src/safety/audit.js +108 -8
- package/src/safety/authcmd.js +75 -5
- package/src/safety/guard.js +507 -15
- package/src/safety/hooks.js +172 -20
- package/src/safety/keystore.js +178 -18
- package/src/safety/network.js +209 -6
- package/src/safety/policy.js +377 -36
- package/src/safety/runmode.js +9 -2
- package/src/safety/secrets.js +243 -17
- package/src/safety/shellenv.js +64 -3
- package/src/safety/trust.js +298 -32
- package/src/safety/undo.js +409 -17
- package/src/setup.js +175 -17
- package/src/skills/discover.js +122 -19
- package/src/stats.js +57 -16
- package/src/tools/{/355/231/225/354/235/270/353/262/225.js → checkmethods.js} +26 -1
- package/src/tools/clipboard.js +53 -20
- package/src/tools/convert.js +118 -37
- package/src/tools/desc.en.js +48 -5
- package/src/tools/doc2md.js +11 -1
- package/src/tools/docs.js +94 -18
- package/src/tools/edit-match.js +212 -13
- package/src/tools/encoding.js +525 -28
- package/src/tools/excel-com.js +63 -15
- package/src/tools/excel.js +12 -7
- package/src/tools/fastgrep.js +444 -36
- package/src/tools/fig.js +40 -9
- package/src/tools/fsutil.js +235 -21
- package/src/tools/hwpxwrite.js +80 -19
- package/src/tools/ignore.js +55 -7
- package/src/tools/index.js +1029 -145
- package/src/tools/jobs.js +325 -50
- package/src/tools/kiwi.js +50 -5
- package/src/tools/lsp.js +164 -32
- package/src/tools/outline.js +52 -12
- package/src/tools/pdf.js +366 -56
- package/src/tools/shell.js +12 -4
- package/src/tools/spawn.js +203 -18
- package/src/tools/todo.js +73 -4
- package/src/tools/verify.js +312 -43
- package/src/tools/webfetch.js +184 -25
- package/src/tools/xlsx.js +232 -31
- package/src/ui/ansi.js +209 -15
- package/src/ui/banner.js +2 -1
- package/src/ui/complete.js +46 -4
- package/src/ui/diff.js +4 -2
- package/src/ui/export.js +91 -17
- package/src/ui/inputbox.js +58 -18
- package/src/ui/intro.js +9 -2
- package/src/ui/level.js +32 -7
- package/src/ui/md.js +101 -15
- package/src/ui/motion.js +5 -2
- package/src/ui/notify.js +7 -1
- package/src/ui/office.js +37 -5
- package/src/ui/pastechip.js +3 -2
- package/src/ui/pick.js +85 -12
- package/src/ui/prompt.js +176 -13
- package/src/ui/screen.js +2 -1
- package/src/ui/spinner.js +6 -1
- package/src/ui/status.js +80 -12
- package/src/ui/wrap.js +23 -37
- /package/src/agent/{/353/213/250/352/263/204.js" → phase.js} +0 -0
- /package/src/skills/builtin/{/352/271/212/354/235/264/354/236/210/352/262/214-/353/247/214/353/223/244/352/270/260/SKILL.md" → build-deep/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/260/250/352/267/274/354/260/250/352/267/274-/353/224/224/353/262/204/352/271/205/SKILL.md" → debug-step-by-step/SKILL.md} +0 -0
- /package/src/skills/builtin/{/353/201/235/352/271/214/354/247/200-/355/225/230/352/270/260/SKILL.md" → finish-all/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/212/244/354/212/244/353/241/234-/352/262/200/355/206/240/SKILL.md" → self-review/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/275/224/353/223/234-/354/244/204/354/235/264/352/270/260/SKILL.md" → simplify-code/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/260/224/353/237/254/353/263/264/352/270/260/SKILL.md" → spike/SKILL.md} +0 -0
- /package/src/skills/builtin/{/352/262/200/354/202/254-/353/250/274/354/240/200/SKILL.md" → test-first/SKILL.md} +0 -0
|
@@ -123,6 +123,29 @@ const 검사파일 = new RegExp(
|
|
|
123
123
|
`^(?:${검사말}[-_][^.]+|.+[._]${뒤에붙는말}|${뒤에붙는말})\\.${검사끝}$`, 'i');
|
|
124
124
|
const 부르는법 = { '.js': 'node', '.mjs': 'node', '.cjs': 'node', '.ts': 'node', '.py': 'python', '.sh': 'sh' };
|
|
125
125
|
|
|
126
|
+
/*
|
|
127
|
+
* ── 여기서 만든 글은 그대로 **셸 명령**이 된다 ──────────────────────────
|
|
128
|
+
*
|
|
129
|
+
* tools/verify.js 가 이 목록을 「전부 Bash 로 돌려라」 와 함께 모델에게 붙인다.
|
|
130
|
+
* 그런데 파일 이름을 정하는 것은 우리가 아니라 **그 저장소**다. 남의 저장소를
|
|
131
|
+
* 열어 보는 것이 이 프로그램의 일이니, 거기 적힌 이름은 믿을 글이 아니다.
|
|
132
|
+
*
|
|
133
|
+
* sh scripts/test-a;echo x.sh 명령이 둘이 된다
|
|
134
|
+
* sh scripts/qa-b$(id).sh sh 가 뜨기도 전에 돈다
|
|
135
|
+
* node test/my unit.test.js 빈칸 하나로 엉뚱한 파일을 찾다 죽는다
|
|
136
|
+
*
|
|
137
|
+
* 그래서 감싸면 한 낱말로 남는 것은 겹따옴표로 감싸고, 감싸도 안 되는 것
|
|
138
|
+
* (겹따옴표·백틱·$·역슬래시·줄바꿈)은 **아예 안 알려 준다.** 홑따옴표는
|
|
139
|
+
* 겹따옴표 안에서 그냥 글자라 감싸는 쪽으로 남긴다 — 막을 까닭이 없는 것을
|
|
140
|
+
* 막으면 그것도 없는 것을 말하는 셈이다.
|
|
141
|
+
*/
|
|
142
|
+
const 감싸도새는글자 = /["`$\\\r\n]/;
|
|
143
|
+
const 그냥써도되는길 = /^[A-Za-z0-9._\-/¡-]+$/;
|
|
144
|
+
function 셸에실을길(길) {
|
|
145
|
+
if (감싸도새는글자.test(길)) return null;
|
|
146
|
+
return 그냥써도되는길.test(길) ? 길 : `"${길}"`;
|
|
147
|
+
}
|
|
148
|
+
|
|
126
149
|
/*
|
|
127
150
|
* 다른 생태계의 관례.
|
|
128
151
|
*
|
|
@@ -187,7 +210,9 @@ export function 확인법들(뿌리) {
|
|
|
187
210
|
const 끝 = 이름.slice(이름.lastIndexOf('.')).toLowerCase();
|
|
188
211
|
const 앞 = 부르는법[끝];
|
|
189
212
|
if (!앞) continue;
|
|
190
|
-
|
|
213
|
+
const 길 = 셸에실을길(`${폴더}/${이름}`);
|
|
214
|
+
if (!길) continue;
|
|
215
|
+
담기(`${앞} ${길}`, `${폴더}/`);
|
|
191
216
|
}
|
|
192
217
|
}
|
|
193
218
|
|
package/src/tools/clipboard.js
CHANGED
|
@@ -34,8 +34,8 @@ import { 치수읽기, 픽셀한도 } from '../backend/vision.js';
|
|
|
34
34
|
/** 이보다 큰 그림은 안 받는다. 화면 캡처 한 장은 보통 1MB 안쪽이다. */
|
|
35
35
|
export const 그림한도 = 12 * 1024 * 1024;
|
|
36
36
|
|
|
37
|
-
const 있나 = (이름, 인자) => {
|
|
38
|
-
const r =
|
|
37
|
+
const 있나 = (부르기, 이름, 인자) => {
|
|
38
|
+
const r = 부르기(이름, 인자, { encoding: 'utf8', timeout: 5000, windowsHide: true });
|
|
39
39
|
return !r.error && r.status === 0;
|
|
40
40
|
};
|
|
41
41
|
|
|
@@ -46,24 +46,31 @@ const 있나 = (이름, 인자) => {
|
|
|
46
46
|
* 셸을 두 번 거치면서 깨지고, 그 깨짐이 PC 마다 다르게 나타난다. 스크립트는
|
|
47
47
|
* ASCII 로만 쓴다 — Windows PowerShell 5.1 은 BOM 없는 .ps1 을 그 PC 의 옛
|
|
48
48
|
* 코드페이지로 읽어서, 한글이 섞이면 문법이 무너진다(src/completion.js 참고).
|
|
49
|
+
*
|
|
50
|
+
* 그래서 **저장할 경로는 스크립트 글에 넣지 않고 인자로 넘긴다.** 앞서는
|
|
51
|
+
* 경로를 글에 박아 넣었는데, 임시 폴더는 사용자 폴더 아래라 계정 이름이
|
|
52
|
+
* 한글인 PC 에서는 그 경로가 latin1 로 적히며 깨졌다 — 그림이 엉뚱한 자리로
|
|
53
|
+
* 저장되고 사람은 「클립보드를 못 읽었습니다」 만 봤다. 인자는 운영체제가
|
|
54
|
+
* 유니코드 그대로 넘겨 주니 경로가 무엇이든 안 깨진다.
|
|
49
55
|
*/
|
|
50
|
-
function 윈도우에서(
|
|
56
|
+
function 윈도우에서(방, 부르기) {
|
|
51
57
|
const 낼곳 = join(방, 'clip.png');
|
|
52
58
|
const 스크립트 = join(방, 'grab.ps1');
|
|
53
59
|
const 글 = [
|
|
60
|
+
'param([string]$Out)',
|
|
54
61
|
'$ErrorActionPreference = "Stop"',
|
|
55
62
|
'Add-Type -AssemblyName System.Windows.Forms',
|
|
56
63
|
'Add-Type -AssemblyName System.Drawing',
|
|
57
64
|
'$img = [System.Windows.Forms.Clipboard]::GetImage()',
|
|
58
65
|
'if ($img -eq $null) { Write-Output "NOIMAGE"; exit 0 }',
|
|
59
|
-
|
|
66
|
+
'$img.Save($Out, [System.Drawing.Imaging.ImageFormat]::Png)',
|
|
60
67
|
'Write-Output "OK"',
|
|
61
68
|
].join('\n');
|
|
62
|
-
// Buffer 로 적어서 인코딩이 끼어들 자리를 없앤다.
|
|
69
|
+
// Buffer 로 적어서 인코딩이 끼어들 자리를 없앤다. 글은 ASCII 뿐이다 — 경로는 param 으로만 들어온다.
|
|
63
70
|
writeFileSync(스크립트, Buffer.from(글, 'latin1'));
|
|
64
71
|
|
|
65
|
-
const r =
|
|
66
|
-
'-NoProfile', '-NonInteractive', '-STA', '-ExecutionPolicy', 'Bypass', '-File', 스크립트,
|
|
72
|
+
const r = 부르기('powershell', [
|
|
73
|
+
'-NoProfile', '-NonInteractive', '-STA', '-ExecutionPolicy', 'Bypass', '-File', 스크립트, 낼곳,
|
|
67
74
|
], { encoding: 'utf8', timeout: 20000, windowsHide: true });
|
|
68
75
|
|
|
69
76
|
if (r.error) return { ok: false, 왜: `파워셸을 못 불렀습니다: ${r.error.message}` };
|
|
@@ -81,10 +88,14 @@ function 윈도우에서(방) {
|
|
|
81
88
|
*
|
|
82
89
|
* osascript 로 PNG 를 꺼내면 «data PNGf89504e47…» 꼴의 16진수 글이 나온다.
|
|
83
90
|
* 그림이 없으면 오류를 내므로, 그것으로 「없음」을 가른다.
|
|
91
|
+
*
|
|
92
|
+
* 16진수는 바이트의 두 배다. 받을 자리(maxBuffer)를 기본값 1MB 로 두었더니
|
|
93
|
+
* PNG 512KB 만 넘어도 ENOBUFS 로 끊겼다 — 레티나 화면 캡처는 대개 그보다 커서
|
|
94
|
+
* 맥에서는 보통 캡처가 안 붙었다. 그림 한도의 두 배에 여유를 더해 잡는다.
|
|
84
95
|
*/
|
|
85
|
-
function 맥에서() {
|
|
86
|
-
const r =
|
|
87
|
-
encoding: 'utf8', timeout: 20000,
|
|
96
|
+
function 맥에서(부르기) {
|
|
97
|
+
const r = 부르기('osascript', ['-e', 'the clipboard as «class PNGf»'], {
|
|
98
|
+
encoding: 'utf8', timeout: 20000, maxBuffer: 그림한도 * 2 + 1024 * 1024,
|
|
88
99
|
});
|
|
89
100
|
if (r.error) return { ok: false, 왜: `osascript 를 못 불렀습니다: ${r.error.message}` };
|
|
90
101
|
const 낸말 = String(r.stdout ?? '');
|
|
@@ -92,7 +103,17 @@ function 맥에서() {
|
|
|
92
103
|
if (!m) {
|
|
93
104
|
// 그림이 아니면 osascript 가 오류를 낸다. 그건 「없음」이지 고장이 아니다.
|
|
94
105
|
if (r.status !== 0) return { ok: false, 없음: true };
|
|
95
|
-
|
|
106
|
+
/*
|
|
107
|
+
* 0 으로 끝났는데 PNG 가 안 나온 것은 **우리 탓**이다 (머리말 「둘을 갈라
|
|
108
|
+
* 말한다」). 여태 두 갈래를 써 놓고 글자까지 같은 말을 돌려줬다 — 「없음」
|
|
109
|
+
* 으로 덮이면 사람은 같은 캡처를 되풀이하고, 될 리가 없다.
|
|
110
|
+
*/
|
|
111
|
+
const 탈 = String(r.stderr ?? '').split('\n').find((l) => l.trim())?.trim() ?? '';
|
|
112
|
+
return {
|
|
113
|
+
ok: false,
|
|
114
|
+
왜: `클립보드에서 그림을 못 꺼냈습니다${탈 ? ` (${탈})` : ''}`
|
|
115
|
+
+ ' — 캡처를 파일로 저장한 뒤 @경로 로 붙이세요.',
|
|
116
|
+
};
|
|
96
117
|
}
|
|
97
118
|
return { ok: true, buf: Buffer.from(m[1], 'hex'), mime: 'image/png' };
|
|
98
119
|
}
|
|
@@ -103,12 +124,12 @@ function 맥에서() {
|
|
|
103
124
|
* 웨이랜드면 wl-paste, X11 이면 xclip. 배포판이 기본으로 안 깔아 주는 것이라
|
|
104
125
|
* 없을 수 있고, 그때는 **무엇을 깔면 되는지** 알려 준다.
|
|
105
126
|
*/
|
|
106
|
-
function 리눅스에서() {
|
|
127
|
+
function 리눅스에서(부르기) {
|
|
107
128
|
const 후보 = [
|
|
108
129
|
{ 이름: 'wl-paste', 볼것: ['--list-types'], 꺼내기: ['--type', 'image/png'] },
|
|
109
130
|
{ 이름: 'xclip', 볼것: ['-selection', 'clipboard', '-t', 'TARGETS', '-o'], 꺼내기: ['-selection', 'clipboard', '-t', 'image/png', '-o'] },
|
|
110
131
|
];
|
|
111
|
-
const 있는것 = 후보.filter((x) => 있나(x.이름, ['--version']) || 있나(x.이름, ['-version']));
|
|
132
|
+
const 있는것 = 후보.filter((x) => 있나(부르기, x.이름, ['--version']) || 있나(부르기, x.이름, ['-version']));
|
|
112
133
|
if (!있는것.length) {
|
|
113
134
|
return {
|
|
114
135
|
ok: false,
|
|
@@ -118,13 +139,25 @@ function 리눅스에서() {
|
|
|
118
139
|
+ ' 둘 다 안 되면 캡처를 파일로 저장한 뒤 @경로 로 붙이세요.',
|
|
119
140
|
};
|
|
120
141
|
}
|
|
142
|
+
/*
|
|
143
|
+
* 그림이 있다고 본 뒤 꺼내기가 막힌 것(시간 초과 · 너무 큼 · 도구 오류)은
|
|
144
|
+
* 「없음」 이 아니다. 앞서는 그것도 끝의 「없음」 으로 흘러서 사람은 캡처만
|
|
145
|
+
* 되풀이했다 — 다시 찍어도 같은 자리에서 또 막히는데. 다음 도구로 한 번 더
|
|
146
|
+
* 해 보고, 끝내 못 꺼냈으면 그 까닭을 말한다.
|
|
147
|
+
*/
|
|
148
|
+
let 못꺼냄 = null;
|
|
121
149
|
for (const x of 있는것) {
|
|
122
|
-
const 종류 =
|
|
150
|
+
const 종류 = 부르기(x.이름, x.볼것, { encoding: 'utf8', timeout: 10000 });
|
|
123
151
|
if (!/image\/png/i.test(String(종류.stdout ?? ''))) continue;
|
|
124
|
-
const r =
|
|
125
|
-
if (r.error || !r.stdout?.length)
|
|
152
|
+
const r = 부르기(x.이름, x.꺼내기, { timeout: 20000, maxBuffer: 64 * 1024 * 1024 });
|
|
153
|
+
if (r.error || !r.stdout?.length) {
|
|
154
|
+
const 탈 = r.error?.message || String(r.stderr ?? '').split('\n').find((l) => l.trim())?.trim() || '빈 응답';
|
|
155
|
+
못꺼냄 ??= `${x.이름}: ${탈}`;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
126
158
|
return { ok: true, buf: Buffer.from(r.stdout), mime: 'image/png' };
|
|
127
159
|
}
|
|
160
|
+
if (못꺼냄) return { ok: false, 왜: `클립보드에 그림은 있는데 꺼내지 못했습니다 (${못꺼냄}) — 캡처를 파일로 저장한 뒤 @경로 로 붙이세요.` };
|
|
128
161
|
return { ok: false, 없음: true };
|
|
129
162
|
}
|
|
130
163
|
|
|
@@ -135,16 +168,16 @@ function 리눅스에서() {
|
|
|
135
168
|
* | {ok:false, 없음:true} 그림이 없다 (사람이 다시 캡처하면 된다)
|
|
136
169
|
* | {ok:false, 왜:string}} 못 꺼냈다 (까닭과 길을 같이 준다)
|
|
137
170
|
*/
|
|
138
|
-
export function 클립보드그림({ platform = process.platform } = {}) {
|
|
171
|
+
export function 클립보드그림({ platform = process.platform, 부르기 = spawnSync } = {}) {
|
|
139
172
|
let 방 = null;
|
|
140
173
|
try {
|
|
141
174
|
if (platform === 'win32') {
|
|
142
175
|
방 = mkdtempSync(join(tmpdir(), 'deel-clip-'));
|
|
143
|
-
const r = 윈도우에서(
|
|
176
|
+
const r = 윈도우에서(방, 부르기);
|
|
144
177
|
return 잰다(r);
|
|
145
178
|
}
|
|
146
|
-
if (platform === 'darwin') return 잰다(맥에서());
|
|
147
|
-
if (platform === 'linux') return 잰다(리눅스에서());
|
|
179
|
+
if (platform === 'darwin') return 잰다(맥에서(부르기));
|
|
180
|
+
if (platform === 'linux') return 잰다(리눅스에서(부르기));
|
|
148
181
|
return { ok: false, 왜: `${platform} 에서는 아직 클립보드 그림을 못 꺼냅니다 — 파일로 저장한 뒤 @경로 로 붙이세요.` };
|
|
149
182
|
} catch (err) {
|
|
150
183
|
return { ok: false, 왜: `클립보드를 읽다 막혔습니다: ${err.message}` };
|
package/src/tools/convert.js
CHANGED
|
@@ -33,7 +33,9 @@ import { basename, extname, join } from 'node:path';
|
|
|
33
33
|
|
|
34
34
|
/** 변환해서 읽어 볼 만한 확장자. 여기 없는 것은 손대지 않는다. */
|
|
35
35
|
const 바꿔볼확장자 = new Set([
|
|
36
|
-
|
|
36
|
+
// 옛 Office (OLE 복합문서). .xlt 는 .xls 의 서식판 — 아래 직접못읽는확장자 에만 있어서
|
|
37
|
+
// 변환기가 있어도 안 불렀다 (2.0.0 6회차 CV1). 그 표는 이 표의 부분집합이어야 한다.
|
|
38
|
+
'.ppt', '.doc', '.xls', '.xlt',
|
|
37
39
|
'.rtf', '.odt', '.odp', '.ods', '.wpd',
|
|
38
40
|
'.pptx', '.docx', '.xlsx', // 겉만 그 이름이고 속이 다른 것
|
|
39
41
|
'.hwp', // 구형 한글 — soffice 가 읽는 판이 있다
|
|
@@ -102,6 +104,22 @@ function 명령짓기(cmd, 인자) {
|
|
|
102
104
|
};
|
|
103
105
|
}
|
|
104
106
|
|
|
107
|
+
/*
|
|
108
|
+
* ── 윈도우에서는 이름 하나만 물어봐서는 **못 찾는다** ──────────────────
|
|
109
|
+
*
|
|
110
|
+
* scoop·choco 로 깔면 PATH 에 놓이는 것은 `soffice.cmd` 껍데기다. Node 의
|
|
111
|
+
* spawn 은 PATHEXT 를 안 보므로 `soffice` 로 물으면 ENOENT 가 나고(그 껍데기를
|
|
112
|
+
* 그냥 부르면 EINVAL 이다), 그러면 우리는 사람이 눈앞에서 쓰고 있는 프로그램을
|
|
113
|
+
* 두고 「이 PC 에 변환기가 없습니다」 라고 말한다 — 바로 위 명령짓기 머리말이
|
|
114
|
+
* 다루겠다고 적어 둔 그 껍데기다. 찾을 때도 확장자까지 붙여 물어본다.
|
|
115
|
+
* 부르는 것은 이미 된다 — `.cmd`·`.bat` 는 명령짓기 가 cmd.exe 로 감싼다.
|
|
116
|
+
*/
|
|
117
|
+
export function soffice이름들(platform = process.platform) {
|
|
118
|
+
return platform === 'win32'
|
|
119
|
+
? ['soffice.exe', 'soffice.com', 'soffice.cmd', 'soffice.bat', 'soffice']
|
|
120
|
+
: ['soffice'];
|
|
121
|
+
}
|
|
122
|
+
|
|
105
123
|
/*
|
|
106
124
|
* 변환은 **비동기로** 부른다.
|
|
107
125
|
*
|
|
@@ -146,8 +164,10 @@ export function 변환기찾기({ 다시 = false, env = process.env, platform =
|
|
|
146
164
|
return 본것;
|
|
147
165
|
}
|
|
148
166
|
let soffice = null;
|
|
149
|
-
|
|
150
|
-
|
|
167
|
+
for (const 이름 of soffice이름들(platform)) {
|
|
168
|
+
if (돌아가나(이름, ['--version'])) { soffice = 이름; break; }
|
|
169
|
+
}
|
|
170
|
+
if (!soffice) {
|
|
151
171
|
for (const p of soffice자리) {
|
|
152
172
|
if (existsSync(p) && 돌아가나(p, ['--version'])) { soffice = p; break; }
|
|
153
173
|
}
|
|
@@ -184,22 +204,36 @@ export function 변환기말(찾은것 = 변환기찾기()) {
|
|
|
184
204
|
*/
|
|
185
205
|
export function 못바꿈말(보인이름, 확장자, 찾은것 = 변환기찾기()) {
|
|
186
206
|
const 갈래 = String(확장자 ?? '').replace(/^\./, '').toLowerCase();
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
207
|
+
/*
|
|
208
|
+
* 「바꿔 봤다」 는 **이 갈래를 받는 변환기가 있을 때만** 적는다 (2.0.0 6회차 CV3·CV4).
|
|
209
|
+
*
|
|
210
|
+
* 변환기가 하나라도 있으면 해 본 것으로 쳤다. 맥은 textutil 이 늘 있는데 textutil 은
|
|
211
|
+
* doc·rtf·odt·docx 만 받는다 — 그래서 맥의 .ppt·.xls·.hwp 가 전부 **안 해 본 것**을
|
|
212
|
+
* 「바꿔 봤지만 글이 안 나왔습니다」 로 받았고 LibreOffice 안내도 빠졌다. 그리고
|
|
213
|
+
* DEEL_CONVERT=off 로 일부러 끈 사람에게 「설치하면 빌려 씁니다」 라고 했다.
|
|
214
|
+
*/
|
|
215
|
+
const 껐나 = !찾은것.soffice && !찾은것.textutil && /DEEL_CONVERT=off/.test(String(찾은것.왜 ?? ''));
|
|
216
|
+
const 받는것있나 = !!찾은것.soffice || (!!찾은것.textutil && textutil갈래.has(`.${갈래}`));
|
|
217
|
+
const 길 = 껐나 ? `${찾은것.왜} — 그래서 바꿔 읽지 않았습니다.`
|
|
218
|
+
: 받는것있나 ? '이 PC 의 변환기로 바꿔 봤지만 글이 안 나왔습니다.'
|
|
219
|
+
: 찾은것.textutil ? '이 PC 의 textutil 은 이 형식을 못 바꾸고, LibreOffice(soffice)는 없습니다.'
|
|
220
|
+
: '이 PC 에 LibreOffice(soffice)가 없어서 바꿔 읽을 수도 없습니다.';
|
|
191
221
|
return `${보인이름} 은 deel 이 직접 못 읽는 형식입니다 (.${갈래}).\n`
|
|
192
222
|
+ `${길}\n`
|
|
193
223
|
+ `해결: 원래 프로그램에서 ${새이름(갈래)} 로 저장한 뒤 다시 주세요.`
|
|
194
|
-
+ (
|
|
224
|
+
+ (!껐나 && !받는것있나 ? ' 또는 LibreOffice 를 설치하면 deel 이 빌려 씁니다.' : '')
|
|
195
225
|
+ '\n**같은 파일을 다시 Read 하지 마세요. 결과는 같습니다.**';
|
|
196
226
|
}
|
|
197
227
|
|
|
228
|
+
/** textutil(맥)이 받는 갈래. 글로바꾸기 와 못바꿈말 이 같은 표를 본다 — 따로 적으면 한쪽만 고쳐진다. */
|
|
229
|
+
const textutil갈래 = new Set(['.doc', '.rtf', '.odt', '.docx']);
|
|
230
|
+
|
|
198
231
|
/** 그 갈래를 무엇으로 저장하면 읽히는지. */
|
|
199
232
|
function 새이름(갈래) {
|
|
200
233
|
if (갈래 === 'ppt') return 'pptx';
|
|
201
234
|
if (갈래 === 'doc' || 갈래 === 'rtf' || 갈래 === 'odt' || 갈래 === 'wpd') return 'docx';
|
|
202
|
-
|
|
235
|
+
// .xlt 는 .xls 의 서식판이다 — 「pdf 나 txt」 로 떨어지면 표가 사라진다 (CV1).
|
|
236
|
+
if (갈래 === 'xls' || 갈래 === 'xlt' || 갈래 === 'ods') return 'xlsx';
|
|
203
237
|
if (갈래 === 'odp') return 'pptx';
|
|
204
238
|
if (갈래 === 'hwp') return 'hwpx';
|
|
205
239
|
return 'pdf 나 txt';
|
|
@@ -236,7 +270,7 @@ export async function 글로바꾸기(abs, root, { timeout = 90000, 찾은것 =
|
|
|
236
270
|
* OS 에 붙어 있는 것이라 곧바로 답한다. 둘 다 되는 자리면 빠른 쪽이 맞다.
|
|
237
271
|
*/
|
|
238
272
|
const 확장자 = extname(abs).toLowerCase();
|
|
239
|
-
if (있는것.textutil &&
|
|
273
|
+
if (있는것.textutil && textutil갈래.has(확장자)) {
|
|
240
274
|
const 나온것 = join(받을곳, `${basename(abs, 확장자)}.txt`);
|
|
241
275
|
const r = await 부르기('textutil', ['-convert', 'txt', '-output', 나온것, abs], { timeout, signal });
|
|
242
276
|
if (!r.error && r.status === 0 && existsSync(나온것)) {
|
|
@@ -259,44 +293,91 @@ export async function 글로바꾸기(abs, root, { timeout = 90000, 찾은것 =
|
|
|
259
293
|
* 있으면 새 부탁을 그쪽에 넘기고 **곧바로 끝나 버린다.** 사람이 LibreOffice 를
|
|
260
294
|
* 열어 둔 PC 에서 변환이 조용히 아무것도 안 하는 것이 그 모습이다. 우리 몫의
|
|
261
295
|
* 프로필을 따로 주면 그 일이 안 생긴다.
|
|
296
|
+
*
|
|
297
|
+
* ── 성공과 실패를 무엇으로 가르나 ──────────────────────────────────
|
|
298
|
+
*
|
|
299
|
+
* **종료코드 0 과 결과 파일, 둘 다** 있어야 성공이다. textutil 쪽이 이미
|
|
300
|
+
* 그 규칙이고(`status === 0 && existsSync`), 여기만 한쪽씩 봤다가 두 자리가
|
|
301
|
+
* 서로 **반대 방향**으로 틀려 있었다 (8회차) —
|
|
302
|
+
*
|
|
303
|
+
* · 종료코드 1 로 죽으면서 반쪽 txt 를 남긴 것을 `ok:true` 로 돌려줬다.
|
|
304
|
+
* 그 깨진 글이 그대로 모델에게 갔고, 모델은 그것이 문서 전부인 줄 안다.
|
|
305
|
+
* · 멀쩡히 끝냈는데 받을곳에 **지난번 같은 이름 txt** 가 남아 있으면
|
|
306
|
+
* 늘어난 것이 없다며 「글을 못 뽑았습니다」 — 성공을 실패로 뒤집었다.
|
|
307
|
+
*
|
|
308
|
+
* 그래서 넣기 전에 우리가 지난번에 남긴 같은 이름 txt 를 먼저 치운다. 결과를
|
|
309
|
+
* 그 이름으로 **찾지는 않는다** — 치우는 것은 우리 사본이고, 나온 것을 찾는
|
|
310
|
+
* 일은 여전히 늘어난 파일로 한다.
|
|
262
311
|
*/
|
|
263
312
|
const 프로필 = join(받을곳, '.soffice-profile');
|
|
313
|
+
try { rmSync(join(받을곳, `${basename(abs, 확장자)}.txt`), { force: true }); } catch { /* 못 지우면 아래에서 걸린다 */ }
|
|
264
314
|
const 전 = new Set(existsSync(받을곳) ? readdirSync(받을곳) : []);
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
315
|
+
try {
|
|
316
|
+
const r = await 부르기(있는것.soffice, [
|
|
317
|
+
`-env:UserInstallation=file:///${프로필.replace(/\\/g, '/').replace(/^\/+/, '')}`,
|
|
318
|
+
'--headless', '--norestore',
|
|
319
|
+
'--convert-to', 'txt:Text',
|
|
320
|
+
'--outdir', 받을곳,
|
|
321
|
+
abs,
|
|
322
|
+
], { timeout, signal });
|
|
272
323
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (!새로생긴것.length) {
|
|
324
|
+
if (r.error) {
|
|
325
|
+
return { ok: false, 왜: `변환기를 못 돌렸습니다: ${r.error.message}` };
|
|
326
|
+
}
|
|
327
|
+
const 새로생긴것 = (existsSync(받을곳) ? readdirSync(받을곳) : [])
|
|
328
|
+
.filter((f) => !전.has(f) && f.toLowerCase().endsWith('.txt'));
|
|
279
329
|
const 끄트머리 = String(r.stderr || r.stdout || '').trim().split('\n').slice(-2).join(' ').slice(0, 200);
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
330
|
+
if (r.status !== 0) {
|
|
331
|
+
// 죽으면서 남긴 반쪽 글도 **사람 문서의 조각**이다. 실패한 자리에 남기지 않는다.
|
|
332
|
+
for (const f of 새로생긴것) { try { rmSync(join(받을곳, f), { force: true }); } catch { /* 임시치우기가 거둔다 */ } }
|
|
333
|
+
return { ok: false, 왜: `변환기가 종료 ${r.status ?? '알 수 없음'} 로 끝났습니다${끄트머리 ? ` (${끄트머리})` : ''}` };
|
|
334
|
+
}
|
|
335
|
+
if (!새로생긴것.length) {
|
|
336
|
+
return { ok: false, 왜: `변환기가 글을 못 뽑았습니다${끄트머리 ? ` (${끄트머리})` : ''}` };
|
|
337
|
+
}
|
|
338
|
+
const 나온것 = join(받을곳, 새로생긴것[0]);
|
|
339
|
+
let text = '';
|
|
340
|
+
try { text = readFileSync(나온것, 'utf8'); } catch (err) {
|
|
341
|
+
return { ok: false, 왜: `바꾼 글을 못 읽었습니다: ${err.message}` };
|
|
342
|
+
} finally {
|
|
343
|
+
/*
|
|
344
|
+
* 읽었으면 곧바로 지운다.
|
|
345
|
+
*
|
|
346
|
+
* 글은 이미 손에 있다. 남겨 두면 **사람 문서의 알맹이가 사본으로 작업
|
|
347
|
+
* 폴더에 쌓인다** — 그대로 커밋되거나 압축되어 나갈 수 있는 자리다.
|
|
348
|
+
* 나중에 거두겠다는 약속은 세션이 죽으면 안 지켜진다. 지금 지운다.
|
|
349
|
+
*/
|
|
350
|
+
try { rmSync(나온것, { force: true }); } catch { /* 못 지우면 임시치우기가 거둔다 */ }
|
|
351
|
+
}
|
|
352
|
+
return { ok: true, text, 쓴것: 'soffice', 파일: 나온것 };
|
|
286
353
|
} finally {
|
|
287
354
|
/*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
* 나중에 거두겠다는 약속은 세션이 죽으면 안 지켜진다. 지금 지운다.
|
|
355
|
+
* 프로필도 **여기서** 거둔다. 지우는 것이 txt 하나뿐이던 때, 그 안에는
|
|
356
|
+
* 방금 연 문서의 캐시(user/registrymodifications.xcu·backup 따위)가 그대로
|
|
357
|
+
* 남아 작업 폴더에 쌓였다 — 글은 지워 놓고 그 글의 캐시를 남긴 셈이다.
|
|
358
|
+
* 성공하든 실패하든 중단이든 한 번은 지나가라고 finally 에 둔다.
|
|
293
359
|
*/
|
|
294
|
-
try { rmSync(
|
|
360
|
+
try { rmSync(프로필, { recursive: true, force: true }); } catch { /* 못 지우면 임시치우기가 거둔다 */ }
|
|
295
361
|
}
|
|
296
|
-
return { ok: true, text, 쓴것: 'soffice', 파일: 나온것 };
|
|
297
362
|
}
|
|
298
363
|
|
|
299
|
-
/**
|
|
364
|
+
/**
|
|
365
|
+
* 떨궈 둔 것을 거둔다.
|
|
366
|
+
*
|
|
367
|
+
* 「검사와 세션 끝에서 부른다」 고 적혀 있었는데 한동안 **부르는 자리가 검사뿐**이었다.
|
|
368
|
+
* 위 rmSync 들에 붙은 「못 지우면 임시치우기가 거둔다」 가 그래서 빈말이었고, 잠겨서
|
|
369
|
+
* 못 지운 임시 파일은 `.deel/tmp` 에 그대로 쌓였다(윈도우에서 soffice 가 물고 있으면
|
|
370
|
+
* 실제로 그렇게 된다). 앞선 판은 그 사실을 주석에만 적고 부르는 자리를 안 세웠다 —
|
|
371
|
+
* 거짓말하는 주석을 고쳐도 파일은 그대로 쌓인다.
|
|
372
|
+
*
|
|
373
|
+
* 이제 두 모드의 끝맺음이 이 자를 부른다: `src/repl.js` 의 정리 블록과
|
|
374
|
+
* `src/oneshot.js` 의 `내놓기`. 둘 다 조용히 부르고, 못 거둬도 끝맺음은 그대로 간다.
|
|
375
|
+
* (검사: test/convert.test.js 의 「거두는자리」)
|
|
376
|
+
*
|
|
377
|
+
* 우리가 떨군 것만 거둔다 — soffice 프로필과 `.txt` 뿐이다. 이 둘이 이 폴더에
|
|
378
|
+
* 우리가 만드는 것의 **전부**라, 사람이 여기 `.txt` 를 손수 넣어 두지 않는 한
|
|
379
|
+
* 남의 것은 안 건드린다. (`.deel/` 는 우리 살림 폴더고 git 도 안 본다.)
|
|
380
|
+
*/
|
|
300
381
|
export function 임시치우기(root) {
|
|
301
382
|
const 자리 = 임시자리(root);
|
|
302
383
|
if (!existsSync(자리)) return 0;
|
package/src/tools/desc.en.js
CHANGED
|
@@ -35,7 +35,13 @@
|
|
|
35
35
|
export const 도구설명EN = {
|
|
36
36
|
Read: {
|
|
37
37
|
desc: 'Read one file. Line numbers come back with it. You must read a file before editing it.'
|
|
38
|
-
|
|
38
|
+
// 한글 쪽(index.js 의 Read 스키마)과 **같은 약속**이어야 한다. 옛 .xls(OLE)는
|
|
39
|
+
// 우리가 직접 못 읽어서 이 PC 의 엑셀이나 LibreOffice 를 빌려 읽는다.
|
|
40
|
+
// 한 묶음으로 적어 두면, 그 둘이 없는 PC 에서는 모델이 사용자에게 그 파일을
|
|
41
|
+
// 달라고 해 놓고 못 읽어 걸음 하나를 버린다.
|
|
42
|
+
+ ' Excel files (.xlsx/.xlsm) can be read directly too — they come back as CSV per sheet.'
|
|
43
|
+
+ ' The old .xls format needs Excel or LibreOffice on this machine —'
|
|
44
|
+
+ ' without one it says it cannot read the file.'
|
|
39
45
|
+ ' Korean HWP, Word and PowerPoint documents (.hwpx/.docx/.pptx) read directly as well —'
|
|
40
46
|
+ ' they come back as plain text. There is no need to ask the user to export anything.'
|
|
41
47
|
+ ' All of these are read-only here, though.',
|
|
@@ -49,17 +55,39 @@ export const 도구설명EN = {
|
|
|
49
55
|
desc: 'Create a file, or overwrite one completely. Use Edit to change part of a file.'
|
|
50
56
|
+ ' **You can create several files in one call** — pass them as an array in files.'
|
|
51
57
|
+ ' Do that when you are laying out a folder structure. One call per file means one model'
|
|
52
|
-
+ ' round trip per file, and an eight-file skeleton loses minutes to that.'
|
|
58
|
+
+ ' round trip per file, and an eight-file skeleton loses minutes to that.'
|
|
59
|
+
// 한글 쪽(index.js 의 Write 스키마)과 **같은 약속**이어야 한다. 이 표는 설명을
|
|
60
|
+
// 통째로 갈아 끼우니, 여기 없는 줄은 안 옮겨진 것이 아니라 **지워진 것**이다 —
|
|
61
|
+
// 지시말이 ko 가 아니면 한글 문서를 만드는 길이 아예 안 나가고, 사내에 낼
|
|
62
|
+
// 보고서를 달라고 해도 모델은 .md 를 쓰고 끝낸다 (tools/hwpxwrite.js 가 안 닿는다).
|
|
63
|
+
+ ' A path ending in `.hwpx` is written as a **Korean HWP document** (# / ## / ### headings'
|
|
64
|
+
+ ' and bullet lists carry over). Do that when you are asked for a report or minutes to hand'
|
|
65
|
+
+ ' round a Korean office. It works only when creating a new file — an existing document'
|
|
66
|
+
+ ' cannot be edited.',
|
|
53
67
|
params: {
|
|
54
68
|
file_path: 'path to write (single file)',
|
|
55
69
|
content: 'the whole file content (single file)',
|
|
56
70
|
files: 'several files at once. When you use this, leave file_path and content out.',
|
|
57
71
|
},
|
|
72
|
+
/*
|
|
73
|
+
* ── 배열 **속** 칸은 따로 적는다 ──────────────────────────────────────
|
|
74
|
+
*
|
|
75
|
+
* 안 적으면 그 칸만 한국어로 나간다. 하필 이 도구들이 제일 세게 미는 길이
|
|
76
|
+
* 배열 쪽이다 — 「여러 군데는 edits 로 한 번에」 「스무 개를 옮기려고 스무 번
|
|
77
|
+
* 부르지 마라」. 영어로 켠 사람은 제일 비싼 길의 설명서만 못 읽고 있었다.
|
|
78
|
+
*
|
|
79
|
+
* 겉의 말을 그대로 물려주지 않는 까닭은, 겉이 `(single file)` 처럼 **한
|
|
80
|
+
* 개짜리 길**을 가리키는 말이라서다. 그대로 쓰면 배열 안에서 틀린 말이 된다.
|
|
81
|
+
*/
|
|
82
|
+
items: {
|
|
83
|
+
files: { file_path: 'path to write', content: 'the whole file content' },
|
|
84
|
+
},
|
|
58
85
|
},
|
|
59
86
|
Append: {
|
|
60
87
|
desc: 'Append to the end of a file. This is how you build a large file — Write the first part,'
|
|
61
88
|
+ ' then call Append repeatedly until it is complete. Splitting it and landing it for certain'
|
|
62
|
-
+ ' beats trying to fit it in one call and getting cut off.
|
|
89
|
+
+ ' beats trying to fit it in one call and getting cut off. Never resend the part you already'
|
|
90
|
+
+ ' wrote — send only what comes next. No Read needed first — you are only'
|
|
63
91
|
+ ' adding to the end, so there is nothing to read.',
|
|
64
92
|
params: {
|
|
65
93
|
file_path: 'path of the file to append to',
|
|
@@ -78,6 +106,14 @@ export const 도구설명EN = {
|
|
|
78
106
|
replace_all: 'true to replace every occurrence',
|
|
79
107
|
edits: 'several places at once, applied in the order given. When you use this, leave the arguments above out.',
|
|
80
108
|
},
|
|
109
|
+
items: {
|
|
110
|
+
edits: {
|
|
111
|
+
file_path: 'path of the file to edit',
|
|
112
|
+
old_string: 'what to replace',
|
|
113
|
+
new_string: 'what to replace it with',
|
|
114
|
+
replace_all: 'true to replace every occurrence',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
81
117
|
},
|
|
82
118
|
Glob: {
|
|
83
119
|
desc: 'Find files by name pattern. e.g. **/*.js, src/**/*.{ts,tsx}',
|
|
@@ -122,8 +158,9 @@ export const 도구설명EN = {
|
|
|
122
158
|
+ ' cut, narrow the request or raise max_chars and call again.',
|
|
123
159
|
params: {
|
|
124
160
|
url: 'address to read (http/https)',
|
|
125
|
-
|
|
126
|
-
|
|
161
|
+
// The limit sits in the first sentence — narrow windows keep only the first sentences.
|
|
162
|
+
max_chars: 'maximum characters to pull (max 120000). Left out, it is sized to the model. Raise it if the'
|
|
163
|
+
+ ' material comes back cut',
|
|
127
164
|
},
|
|
128
165
|
},
|
|
129
166
|
Move: {
|
|
@@ -136,6 +173,9 @@ export const 도구설명EN = {
|
|
|
136
173
|
moves: 'several at once. With this, from/to are unused',
|
|
137
174
|
overwrite: 'overwrite the destination if it already exists. Default false, so a clash is refused',
|
|
138
175
|
},
|
|
176
|
+
items: {
|
|
177
|
+
moves: { from: 'the file or folder to move', to: 'where it goes. Missing folders are created' },
|
|
178
|
+
},
|
|
139
179
|
},
|
|
140
180
|
Ask: {
|
|
141
181
|
desc: 'Ask the person one question at a genuine fork. **First fill in `understanding` with what you'
|
|
@@ -174,6 +214,9 @@ export const 도구설명EN = {
|
|
|
174
214
|
+ ' is set by the size of the job — there is no fixed count and no cap. Do not squeeze unrelated'
|
|
175
215
|
+ ' work into one line to hit a number. Write each step small enough to check on its own.',
|
|
176
216
|
params: { todos: 'the whole todo list. No cap — as many as the job needs' },
|
|
217
|
+
items: {
|
|
218
|
+
todos: { text: 'one line on what to do', state: 'todo / doing / done' },
|
|
219
|
+
},
|
|
177
220
|
},
|
|
178
221
|
Verify: {
|
|
179
222
|
desc: 'Check that what you made actually works. Call this **before** you finish, without fail.'
|
package/src/tools/doc2md.js
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
import { basename, extname } from 'node:path';
|
|
29
29
|
|
|
30
30
|
import { isDocPath, readDoc, summarize as docSummary } from './docs.js';
|
|
31
|
-
import { isPdfPath, readPdf, summarize as pdfSummary } from './pdf.js';
|
|
31
|
+
import { isPdfPath, readPdf, summarize as pdfSummary, 못읽은말 } from './pdf.js';
|
|
32
32
|
import { isFigPath, readFig, summarize as figSummary } from './fig.js';
|
|
33
33
|
import { readXlsx, toCsv } from './xlsx.js';
|
|
34
34
|
import { readFileSync } from 'node:fs';
|
|
@@ -227,6 +227,16 @@ function pdf를마크다운(경로, maxChars) {
|
|
|
227
227
|
|
|
228
228
|
const 담 = 담개(maxChars);
|
|
229
229
|
담.담기(제목(경로));
|
|
230
|
+
/*
|
|
231
|
+
* 쪽이 통째로 빠진 것·쪽 차례를 못 믿는 것을 **마크다운 안에** 적는다.
|
|
232
|
+
*
|
|
233
|
+
* 여태 이 둘은 `summary` 한 줄에만 있었다. 그런데 doc2md 의 결과물은 파일로
|
|
234
|
+
* 남아 나중에 그것만 읽히는 물건이다 — 요약은 그때 이미 없다. /Count 가 12쪽
|
|
235
|
+
* 이라는데 8쪽만 건진 문서가 「8쪽짜리 온전한 문서」로 남아, 모델이 빠진 쪽을
|
|
236
|
+
* 근거로 「문서에 그런 조항은 없습니다」 라고 답하는 자리가 바로 여기다.
|
|
237
|
+
*/
|
|
238
|
+
const 머리말 = 못읽은말({ ...r, 못읽은쪽: [] });
|
|
239
|
+
if (머리말) 담.담기(머리말.split('\n').map((줄) => `> ${줄.trim()}`).join('\n'));
|
|
230
240
|
const 왜표 = new Map((r.못읽은쪽 ?? []).map((x) => [x.번호, x]));
|
|
231
241
|
for (const d of r.덩이들) {
|
|
232
242
|
const 번호 = Number((d.이름.match(/^(\d+)/) ?? [])[1]);
|