oh-my-til 1.3.1-dev.0 → 1.4.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 +3 -2
- package/README.md +3 -2
- package/dist/cli.js +168 -273
- package/manifest.json +1 -1
- package/package.json +3 -3
package/README.ko.md
CHANGED
|
@@ -15,7 +15,7 @@ AI 기반 TIL(Today I Learned) 학습 워크플로우를 위한 Claude Code 플
|
|
|
15
15
|
- **터미널 임베딩** — Obsidian 사이드바에서 Claude Code 터미널 실행 (xterm.js + node-pty)
|
|
16
16
|
- **MCP 서버 내장** — Claude Code가 HTTP로 vault에 직접 접근 (별도 플러그인 불필요)
|
|
17
17
|
- **학습 대시보드** — TIL 통계, 카테고리별 학습 현황을 한눈에
|
|
18
|
-
- **스킬 자동 설치** — `/til`, `/research`, `/backlog`, `/save`, `/til-review`, `/dashboard` 명령을 바로 사용 가능
|
|
18
|
+
- **스킬 자동 설치** — `/til`, `/research`, `/backlog`, `/save`, `/til-review`, `/til-lint`, `/dashboard` 명령을 바로 사용 가능
|
|
19
19
|
- **간격 반복 복습 (SRS)** — SM-2 알고리즘 기반 TIL 복습 스케줄링
|
|
20
20
|
- **마크다운 링크 감지** — 터미널의 `[텍스트](경로)` 링크를 클릭하면 노트 열기 (CJK 문자 지원)
|
|
21
21
|
- **백로그 → TIL 연동** — 빈 백로그 링크 클릭 시 TIL 학습 세션 시작 제안
|
|
@@ -25,7 +25,7 @@ AI 기반 TIL(Today I Learned) 학습 워크플로우를 위한 Claude Code 플
|
|
|
25
25
|
|
|
26
26
|
```
|
|
27
27
|
커맨드 팔레트 → 터미널 열기 → Claude Code 자동 시작
|
|
28
|
-
→ /til, /backlog, /research, /save, /til-review, /dashboard 스킬 실행
|
|
28
|
+
→ /til, /backlog, /research, /save, /til-review, /til-lint, /dashboard 스킬 실행
|
|
29
29
|
→ Claude가 리서치 → 대화형 학습 → TIL 마크다운 저장
|
|
30
30
|
→ 새 파일 감지 → 에디터에서 자동 열기
|
|
31
31
|
```
|
|
@@ -148,6 +148,7 @@ MCP 서버 연결 시 Claude Code에서 사용할 수 있는 도구:
|
|
|
148
148
|
| **backlog** | `/backlog [카테고리]` | 학습 백로그 조회 및 진행 상황 요약 |
|
|
149
149
|
| **save** | *(/til에서 자동 호출)* | TIL 마크다운 저장 + Daily 노트, MOC, 백로그 연동 |
|
|
150
150
|
| **til-review** | `/til-review [카테고리]` | SRS 기반 간격 반복 복습 세션 (SM-2 알고리즘) |
|
|
151
|
+
| **til-lint** | `/til-lint [카테고리]` | TIL 위키 상태 점검 — frontmatter 누락, 깨진 링크, 고아 TIL, 미처리 raw 자료 |
|
|
151
152
|
| **dashboard** | `/dashboard` | 학습 대시보드 — 통계, 활동 히트맵, 카테고리, 백로그 진행률 |
|
|
152
153
|
| **omt-setup** | `/omt-setup [서브커맨드]` | 통합 설정 — init, deploy, oh-my-til 관리 |
|
|
153
154
|
| **setup-obsidian** | `/setup-obsidian` | 현재 vault에 Obsidian 데스크톱 플러그인 설치 |
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ A Claude Code plugin for AI-powered TIL (Today I Learned) learning workflow. Wor
|
|
|
15
15
|
- **Embedded Terminal** — Claude Code terminal in Obsidian sidebar (xterm.js + node-pty)
|
|
16
16
|
- **Built-in MCP Server** — Claude Code can directly access your vault via HTTP
|
|
17
17
|
- **Learning Dashboard** — TIL statistics and category breakdown at a glance
|
|
18
|
-
- **Auto-installed Skills** — `/til`, `/research`, `/backlog`, `/save`, `/til-review`, `/dashboard` commands ready out of the box
|
|
18
|
+
- **Auto-installed Skills** — `/til`, `/research`, `/backlog`, `/save`, `/til-review`, `/til-lint`, `/dashboard` commands ready out of the box
|
|
19
19
|
- **Spaced Repetition (SRS)** — SM-2 algorithm-based review scheduling for TIL notes
|
|
20
20
|
- **Markdown Link Detection** — `[text](path)` links in terminal are clickable and open notes (CJK-aware)
|
|
21
21
|
- **Backlog-to-TIL Trigger** — Click an empty backlog link to start a TIL session
|
|
@@ -25,7 +25,7 @@ A Claude Code plugin for AI-powered TIL (Today I Learned) learning workflow. Wor
|
|
|
25
25
|
|
|
26
26
|
```
|
|
27
27
|
Command Palette → Open Terminal → Claude Code starts
|
|
28
|
-
→ Run /til, /backlog, /research, /save, /til-review, /dashboard skills
|
|
28
|
+
→ Run /til, /backlog, /research, /save, /til-review, /til-lint, /dashboard skills
|
|
29
29
|
→ Claude researches → interactive learning → saves TIL markdown
|
|
30
30
|
→ New file detected → opens in editor
|
|
31
31
|
```
|
|
@@ -148,6 +148,7 @@ The plugin auto-installs these skills to `.claude/skills/`:
|
|
|
148
148
|
| **backlog** | `/backlog [category]` | View learning backlog and progress |
|
|
149
149
|
| **save** | *(auto-invoked by /til)* | Save TIL markdown with Daily note, MOC, and backlog updates |
|
|
150
150
|
| **til-review** | `/til-review [category]` | SRS-based spaced repetition review session (SM-2 algorithm) |
|
|
151
|
+
| **til-lint** | `/til-lint [category]` | TIL wiki health-check — missing frontmatter, broken links, orphan TILs, unprocessed raw sources |
|
|
151
152
|
| **dashboard** | `/dashboard` | Learning dashboard — stats, activity heatmap, categories, backlog progress |
|
|
152
153
|
| **omt-setup** | `/omt-setup [subcommand]` | Unified setup — init, deploy, and manage oh-my-til |
|
|
153
154
|
| **setup-obsidian** | `/setup-obsidian` | Install the Obsidian desktop plugin in the current vault |
|
package/dist/cli.js
CHANGED
|
@@ -110,17 +110,17 @@ var require_visit = __commonJS({
|
|
|
110
110
|
visit.BREAK = BREAK;
|
|
111
111
|
visit.SKIP = SKIP;
|
|
112
112
|
visit.REMOVE = REMOVE;
|
|
113
|
-
function visit_(key, node, visitor,
|
|
114
|
-
const ctrl = callVisitor(key, node, visitor,
|
|
113
|
+
function visit_(key, node, visitor, path6) {
|
|
114
|
+
const ctrl = callVisitor(key, node, visitor, path6);
|
|
115
115
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
116
|
-
replaceNode(key,
|
|
117
|
-
return visit_(key, ctrl, visitor,
|
|
116
|
+
replaceNode(key, path6, ctrl);
|
|
117
|
+
return visit_(key, ctrl, visitor, path6);
|
|
118
118
|
}
|
|
119
119
|
if (typeof ctrl !== "symbol") {
|
|
120
120
|
if (identity.isCollection(node)) {
|
|
121
|
-
|
|
121
|
+
path6 = Object.freeze(path6.concat(node));
|
|
122
122
|
for (let i = 0; i < node.items.length; ++i) {
|
|
123
|
-
const ci = visit_(i, node.items[i], visitor,
|
|
123
|
+
const ci = visit_(i, node.items[i], visitor, path6);
|
|
124
124
|
if (typeof ci === "number")
|
|
125
125
|
i = ci - 1;
|
|
126
126
|
else if (ci === BREAK)
|
|
@@ -131,13 +131,13 @@ var require_visit = __commonJS({
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
} else if (identity.isPair(node)) {
|
|
134
|
-
|
|
135
|
-
const ck = visit_("key", node.key, visitor,
|
|
134
|
+
path6 = Object.freeze(path6.concat(node));
|
|
135
|
+
const ck = visit_("key", node.key, visitor, path6);
|
|
136
136
|
if (ck === BREAK)
|
|
137
137
|
return BREAK;
|
|
138
138
|
else if (ck === REMOVE)
|
|
139
139
|
node.key = null;
|
|
140
|
-
const cv = visit_("value", node.value, visitor,
|
|
140
|
+
const cv = visit_("value", node.value, visitor, path6);
|
|
141
141
|
if (cv === BREAK)
|
|
142
142
|
return BREAK;
|
|
143
143
|
else if (cv === REMOVE)
|
|
@@ -158,17 +158,17 @@ var require_visit = __commonJS({
|
|
|
158
158
|
visitAsync.BREAK = BREAK;
|
|
159
159
|
visitAsync.SKIP = SKIP;
|
|
160
160
|
visitAsync.REMOVE = REMOVE;
|
|
161
|
-
async function visitAsync_(key, node, visitor,
|
|
162
|
-
const ctrl = await callVisitor(key, node, visitor,
|
|
161
|
+
async function visitAsync_(key, node, visitor, path6) {
|
|
162
|
+
const ctrl = await callVisitor(key, node, visitor, path6);
|
|
163
163
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
164
|
-
replaceNode(key,
|
|
165
|
-
return visitAsync_(key, ctrl, visitor,
|
|
164
|
+
replaceNode(key, path6, ctrl);
|
|
165
|
+
return visitAsync_(key, ctrl, visitor, path6);
|
|
166
166
|
}
|
|
167
167
|
if (typeof ctrl !== "symbol") {
|
|
168
168
|
if (identity.isCollection(node)) {
|
|
169
|
-
|
|
169
|
+
path6 = Object.freeze(path6.concat(node));
|
|
170
170
|
for (let i = 0; i < node.items.length; ++i) {
|
|
171
|
-
const ci = await visitAsync_(i, node.items[i], visitor,
|
|
171
|
+
const ci = await visitAsync_(i, node.items[i], visitor, path6);
|
|
172
172
|
if (typeof ci === "number")
|
|
173
173
|
i = ci - 1;
|
|
174
174
|
else if (ci === BREAK)
|
|
@@ -179,13 +179,13 @@ var require_visit = __commonJS({
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
} else if (identity.isPair(node)) {
|
|
182
|
-
|
|
183
|
-
const ck = await visitAsync_("key", node.key, visitor,
|
|
182
|
+
path6 = Object.freeze(path6.concat(node));
|
|
183
|
+
const ck = await visitAsync_("key", node.key, visitor, path6);
|
|
184
184
|
if (ck === BREAK)
|
|
185
185
|
return BREAK;
|
|
186
186
|
else if (ck === REMOVE)
|
|
187
187
|
node.key = null;
|
|
188
|
-
const cv = await visitAsync_("value", node.value, visitor,
|
|
188
|
+
const cv = await visitAsync_("value", node.value, visitor, path6);
|
|
189
189
|
if (cv === BREAK)
|
|
190
190
|
return BREAK;
|
|
191
191
|
else if (cv === REMOVE)
|
|
@@ -212,23 +212,23 @@ var require_visit = __commonJS({
|
|
|
212
212
|
}
|
|
213
213
|
return visitor;
|
|
214
214
|
}
|
|
215
|
-
function callVisitor(key, node, visitor,
|
|
215
|
+
function callVisitor(key, node, visitor, path6) {
|
|
216
216
|
if (typeof visitor === "function")
|
|
217
|
-
return visitor(key, node,
|
|
217
|
+
return visitor(key, node, path6);
|
|
218
218
|
if (identity.isMap(node))
|
|
219
|
-
return visitor.Map?.(key, node,
|
|
219
|
+
return visitor.Map?.(key, node, path6);
|
|
220
220
|
if (identity.isSeq(node))
|
|
221
|
-
return visitor.Seq?.(key, node,
|
|
221
|
+
return visitor.Seq?.(key, node, path6);
|
|
222
222
|
if (identity.isPair(node))
|
|
223
|
-
return visitor.Pair?.(key, node,
|
|
223
|
+
return visitor.Pair?.(key, node, path6);
|
|
224
224
|
if (identity.isScalar(node))
|
|
225
|
-
return visitor.Scalar?.(key, node,
|
|
225
|
+
return visitor.Scalar?.(key, node, path6);
|
|
226
226
|
if (identity.isAlias(node))
|
|
227
|
-
return visitor.Alias?.(key, node,
|
|
227
|
+
return visitor.Alias?.(key, node, path6);
|
|
228
228
|
return void 0;
|
|
229
229
|
}
|
|
230
|
-
function replaceNode(key,
|
|
231
|
-
const parent =
|
|
230
|
+
function replaceNode(key, path6, node) {
|
|
231
|
+
const parent = path6[path6.length - 1];
|
|
232
232
|
if (identity.isCollection(parent)) {
|
|
233
233
|
parent.items[key] = node;
|
|
234
234
|
} else if (identity.isPair(parent)) {
|
|
@@ -836,10 +836,10 @@ var require_Collection = __commonJS({
|
|
|
836
836
|
var createNode = require_createNode();
|
|
837
837
|
var identity = require_identity();
|
|
838
838
|
var Node = require_Node();
|
|
839
|
-
function collectionFromPath(schema,
|
|
839
|
+
function collectionFromPath(schema, path6, value) {
|
|
840
840
|
let v = value;
|
|
841
|
-
for (let i =
|
|
842
|
-
const k =
|
|
841
|
+
for (let i = path6.length - 1; i >= 0; --i) {
|
|
842
|
+
const k = path6[i];
|
|
843
843
|
if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
|
|
844
844
|
const a = [];
|
|
845
845
|
a[k] = v;
|
|
@@ -858,7 +858,7 @@ var require_Collection = __commonJS({
|
|
|
858
858
|
sourceObjects: /* @__PURE__ */ new Map()
|
|
859
859
|
});
|
|
860
860
|
}
|
|
861
|
-
var isEmptyPath = (
|
|
861
|
+
var isEmptyPath = (path6) => path6 == null || typeof path6 === "object" && !!path6[Symbol.iterator]().next().done;
|
|
862
862
|
var Collection = class extends Node.NodeBase {
|
|
863
863
|
constructor(type, schema) {
|
|
864
864
|
super(type);
|
|
@@ -888,11 +888,11 @@ var require_Collection = __commonJS({
|
|
|
888
888
|
* be a Pair instance or a `{ key, value }` object, which may not have a key
|
|
889
889
|
* that already exists in the map.
|
|
890
890
|
*/
|
|
891
|
-
addIn(
|
|
892
|
-
if (isEmptyPath(
|
|
891
|
+
addIn(path6, value) {
|
|
892
|
+
if (isEmptyPath(path6))
|
|
893
893
|
this.add(value);
|
|
894
894
|
else {
|
|
895
|
-
const [key, ...rest] =
|
|
895
|
+
const [key, ...rest] = path6;
|
|
896
896
|
const node = this.get(key, true);
|
|
897
897
|
if (identity.isCollection(node))
|
|
898
898
|
node.addIn(rest, value);
|
|
@@ -906,8 +906,8 @@ var require_Collection = __commonJS({
|
|
|
906
906
|
* Removes a value from the collection.
|
|
907
907
|
* @returns `true` if the item was found and removed.
|
|
908
908
|
*/
|
|
909
|
-
deleteIn(
|
|
910
|
-
const [key, ...rest] =
|
|
909
|
+
deleteIn(path6) {
|
|
910
|
+
const [key, ...rest] = path6;
|
|
911
911
|
if (rest.length === 0)
|
|
912
912
|
return this.delete(key);
|
|
913
913
|
const node = this.get(key, true);
|
|
@@ -921,8 +921,8 @@ var require_Collection = __commonJS({
|
|
|
921
921
|
* scalar values from their surrounding node; to disable set `keepScalar` to
|
|
922
922
|
* `true` (collections are always returned intact).
|
|
923
923
|
*/
|
|
924
|
-
getIn(
|
|
925
|
-
const [key, ...rest] =
|
|
924
|
+
getIn(path6, keepScalar) {
|
|
925
|
+
const [key, ...rest] = path6;
|
|
926
926
|
const node = this.get(key, true);
|
|
927
927
|
if (rest.length === 0)
|
|
928
928
|
return !keepScalar && identity.isScalar(node) ? node.value : node;
|
|
@@ -940,8 +940,8 @@ var require_Collection = __commonJS({
|
|
|
940
940
|
/**
|
|
941
941
|
* Checks if the collection includes a value with the key `key`.
|
|
942
942
|
*/
|
|
943
|
-
hasIn(
|
|
944
|
-
const [key, ...rest] =
|
|
943
|
+
hasIn(path6) {
|
|
944
|
+
const [key, ...rest] = path6;
|
|
945
945
|
if (rest.length === 0)
|
|
946
946
|
return this.has(key);
|
|
947
947
|
const node = this.get(key, true);
|
|
@@ -951,8 +951,8 @@ var require_Collection = __commonJS({
|
|
|
951
951
|
* Sets a value in this collection. For `!!set`, `value` needs to be a
|
|
952
952
|
* boolean to add/remove the item from the set.
|
|
953
953
|
*/
|
|
954
|
-
setIn(
|
|
955
|
-
const [key, ...rest] =
|
|
954
|
+
setIn(path6, value) {
|
|
955
|
+
const [key, ...rest] = path6;
|
|
956
956
|
if (rest.length === 0) {
|
|
957
957
|
this.set(key, value);
|
|
958
958
|
} else {
|
|
@@ -3456,9 +3456,9 @@ var require_Document = __commonJS({
|
|
|
3456
3456
|
this.contents.add(value);
|
|
3457
3457
|
}
|
|
3458
3458
|
/** Adds a value to the document. */
|
|
3459
|
-
addIn(
|
|
3459
|
+
addIn(path6, value) {
|
|
3460
3460
|
if (assertCollection(this.contents))
|
|
3461
|
-
this.contents.addIn(
|
|
3461
|
+
this.contents.addIn(path6, value);
|
|
3462
3462
|
}
|
|
3463
3463
|
/**
|
|
3464
3464
|
* Create a new `Alias` node, ensuring that the target `node` has the required anchor.
|
|
@@ -3533,14 +3533,14 @@ var require_Document = __commonJS({
|
|
|
3533
3533
|
* Removes a value from the document.
|
|
3534
3534
|
* @returns `true` if the item was found and removed.
|
|
3535
3535
|
*/
|
|
3536
|
-
deleteIn(
|
|
3537
|
-
if (Collection.isEmptyPath(
|
|
3536
|
+
deleteIn(path6) {
|
|
3537
|
+
if (Collection.isEmptyPath(path6)) {
|
|
3538
3538
|
if (this.contents == null)
|
|
3539
3539
|
return false;
|
|
3540
3540
|
this.contents = null;
|
|
3541
3541
|
return true;
|
|
3542
3542
|
}
|
|
3543
|
-
return assertCollection(this.contents) ? this.contents.deleteIn(
|
|
3543
|
+
return assertCollection(this.contents) ? this.contents.deleteIn(path6) : false;
|
|
3544
3544
|
}
|
|
3545
3545
|
/**
|
|
3546
3546
|
* Returns item at `key`, or `undefined` if not found. By default unwraps
|
|
@@ -3555,10 +3555,10 @@ var require_Document = __commonJS({
|
|
|
3555
3555
|
* scalar values from their surrounding node; to disable set `keepScalar` to
|
|
3556
3556
|
* `true` (collections are always returned intact).
|
|
3557
3557
|
*/
|
|
3558
|
-
getIn(
|
|
3559
|
-
if (Collection.isEmptyPath(
|
|
3558
|
+
getIn(path6, keepScalar) {
|
|
3559
|
+
if (Collection.isEmptyPath(path6))
|
|
3560
3560
|
return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
|
|
3561
|
-
return identity.isCollection(this.contents) ? this.contents.getIn(
|
|
3561
|
+
return identity.isCollection(this.contents) ? this.contents.getIn(path6, keepScalar) : void 0;
|
|
3562
3562
|
}
|
|
3563
3563
|
/**
|
|
3564
3564
|
* Checks if the document includes a value with the key `key`.
|
|
@@ -3569,10 +3569,10 @@ var require_Document = __commonJS({
|
|
|
3569
3569
|
/**
|
|
3570
3570
|
* Checks if the document includes a value at `path`.
|
|
3571
3571
|
*/
|
|
3572
|
-
hasIn(
|
|
3573
|
-
if (Collection.isEmptyPath(
|
|
3572
|
+
hasIn(path6) {
|
|
3573
|
+
if (Collection.isEmptyPath(path6))
|
|
3574
3574
|
return this.contents !== void 0;
|
|
3575
|
-
return identity.isCollection(this.contents) ? this.contents.hasIn(
|
|
3575
|
+
return identity.isCollection(this.contents) ? this.contents.hasIn(path6) : false;
|
|
3576
3576
|
}
|
|
3577
3577
|
/**
|
|
3578
3578
|
* Sets a value in this document. For `!!set`, `value` needs to be a
|
|
@@ -3589,13 +3589,13 @@ var require_Document = __commonJS({
|
|
|
3589
3589
|
* Sets a value in this document. For `!!set`, `value` needs to be a
|
|
3590
3590
|
* boolean to add/remove the item from the set.
|
|
3591
3591
|
*/
|
|
3592
|
-
setIn(
|
|
3593
|
-
if (Collection.isEmptyPath(
|
|
3592
|
+
setIn(path6, value) {
|
|
3593
|
+
if (Collection.isEmptyPath(path6)) {
|
|
3594
3594
|
this.contents = value;
|
|
3595
3595
|
} else if (this.contents == null) {
|
|
3596
|
-
this.contents = Collection.collectionFromPath(this.schema, Array.from(
|
|
3596
|
+
this.contents = Collection.collectionFromPath(this.schema, Array.from(path6), value);
|
|
3597
3597
|
} else if (assertCollection(this.contents)) {
|
|
3598
|
-
this.contents.setIn(
|
|
3598
|
+
this.contents.setIn(path6, value);
|
|
3599
3599
|
}
|
|
3600
3600
|
}
|
|
3601
3601
|
/**
|
|
@@ -5543,9 +5543,9 @@ var require_cst_visit = __commonJS({
|
|
|
5543
5543
|
visit.BREAK = BREAK;
|
|
5544
5544
|
visit.SKIP = SKIP;
|
|
5545
5545
|
visit.REMOVE = REMOVE;
|
|
5546
|
-
visit.itemAtPath = (cst,
|
|
5546
|
+
visit.itemAtPath = (cst, path6) => {
|
|
5547
5547
|
let item = cst;
|
|
5548
|
-
for (const [field, index] of
|
|
5548
|
+
for (const [field, index] of path6) {
|
|
5549
5549
|
const tok = item?.[field];
|
|
5550
5550
|
if (tok && "items" in tok) {
|
|
5551
5551
|
item = tok.items[index];
|
|
@@ -5554,23 +5554,23 @@ var require_cst_visit = __commonJS({
|
|
|
5554
5554
|
}
|
|
5555
5555
|
return item;
|
|
5556
5556
|
};
|
|
5557
|
-
visit.parentCollection = (cst,
|
|
5558
|
-
const parent = visit.itemAtPath(cst,
|
|
5559
|
-
const field =
|
|
5557
|
+
visit.parentCollection = (cst, path6) => {
|
|
5558
|
+
const parent = visit.itemAtPath(cst, path6.slice(0, -1));
|
|
5559
|
+
const field = path6[path6.length - 1][0];
|
|
5560
5560
|
const coll = parent?.[field];
|
|
5561
5561
|
if (coll && "items" in coll)
|
|
5562
5562
|
return coll;
|
|
5563
5563
|
throw new Error("Parent collection not found");
|
|
5564
5564
|
};
|
|
5565
|
-
function _visit(
|
|
5566
|
-
let ctrl = visitor(item,
|
|
5565
|
+
function _visit(path6, item, visitor) {
|
|
5566
|
+
let ctrl = visitor(item, path6);
|
|
5567
5567
|
if (typeof ctrl === "symbol")
|
|
5568
5568
|
return ctrl;
|
|
5569
5569
|
for (const field of ["key", "value"]) {
|
|
5570
5570
|
const token = item[field];
|
|
5571
5571
|
if (token && "items" in token) {
|
|
5572
5572
|
for (let i = 0; i < token.items.length; ++i) {
|
|
5573
|
-
const ci = _visit(Object.freeze(
|
|
5573
|
+
const ci = _visit(Object.freeze(path6.concat([[field, i]])), token.items[i], visitor);
|
|
5574
5574
|
if (typeof ci === "number")
|
|
5575
5575
|
i = ci - 1;
|
|
5576
5576
|
else if (ci === BREAK)
|
|
@@ -5581,10 +5581,10 @@ var require_cst_visit = __commonJS({
|
|
|
5581
5581
|
}
|
|
5582
5582
|
}
|
|
5583
5583
|
if (typeof ctrl === "function" && field === "key")
|
|
5584
|
-
ctrl = ctrl(item,
|
|
5584
|
+
ctrl = ctrl(item, path6);
|
|
5585
5585
|
}
|
|
5586
5586
|
}
|
|
5587
|
-
return typeof ctrl === "function" ? ctrl(item,
|
|
5587
|
+
return typeof ctrl === "function" ? ctrl(item, path6) : ctrl;
|
|
5588
5588
|
}
|
|
5589
5589
|
exports2.visit = visit;
|
|
5590
5590
|
}
|
|
@@ -6863,14 +6863,14 @@ var require_parser = __commonJS({
|
|
|
6863
6863
|
case "scalar":
|
|
6864
6864
|
case "single-quoted-scalar":
|
|
6865
6865
|
case "double-quoted-scalar": {
|
|
6866
|
-
const
|
|
6866
|
+
const fs5 = this.flowScalar(this.type);
|
|
6867
6867
|
if (atNextItem || it.value) {
|
|
6868
|
-
map2.items.push({ start, key:
|
|
6868
|
+
map2.items.push({ start, key: fs5, sep: [] });
|
|
6869
6869
|
this.onKeyLine = true;
|
|
6870
6870
|
} else if (it.sep) {
|
|
6871
|
-
this.stack.push(
|
|
6871
|
+
this.stack.push(fs5);
|
|
6872
6872
|
} else {
|
|
6873
|
-
Object.assign(it, { key:
|
|
6873
|
+
Object.assign(it, { key: fs5, sep: [] });
|
|
6874
6874
|
this.onKeyLine = true;
|
|
6875
6875
|
}
|
|
6876
6876
|
return;
|
|
@@ -6998,13 +6998,13 @@ var require_parser = __commonJS({
|
|
|
6998
6998
|
case "scalar":
|
|
6999
6999
|
case "single-quoted-scalar":
|
|
7000
7000
|
case "double-quoted-scalar": {
|
|
7001
|
-
const
|
|
7001
|
+
const fs5 = this.flowScalar(this.type);
|
|
7002
7002
|
if (!it || it.value)
|
|
7003
|
-
fc.items.push({ start: [], key:
|
|
7003
|
+
fc.items.push({ start: [], key: fs5, sep: [] });
|
|
7004
7004
|
else if (it.sep)
|
|
7005
|
-
this.stack.push(
|
|
7005
|
+
this.stack.push(fs5);
|
|
7006
7006
|
else
|
|
7007
|
-
Object.assign(it, { key:
|
|
7007
|
+
Object.assign(it, { key: fs5, sep: [] });
|
|
7008
7008
|
return;
|
|
7009
7009
|
}
|
|
7010
7010
|
case "flow-map-end":
|
|
@@ -10515,8 +10515,8 @@ var require_utils = __commonJS({
|
|
|
10515
10515
|
}
|
|
10516
10516
|
return ind;
|
|
10517
10517
|
}
|
|
10518
|
-
function removeDotSegments(
|
|
10519
|
-
let input =
|
|
10518
|
+
function removeDotSegments(path6) {
|
|
10519
|
+
let input = path6;
|
|
10520
10520
|
const output = [];
|
|
10521
10521
|
let nextSlash = -1;
|
|
10522
10522
|
let len = 0;
|
|
@@ -10715,8 +10715,8 @@ var require_schemes = __commonJS({
|
|
|
10715
10715
|
wsComponent.secure = void 0;
|
|
10716
10716
|
}
|
|
10717
10717
|
if (wsComponent.resourceName) {
|
|
10718
|
-
const [
|
|
10719
|
-
wsComponent.path =
|
|
10718
|
+
const [path6, query] = wsComponent.resourceName.split("?");
|
|
10719
|
+
wsComponent.path = path6 && path6 !== "/" ? path6 : void 0;
|
|
10720
10720
|
wsComponent.query = query;
|
|
10721
10721
|
wsComponent.resourceName = void 0;
|
|
10722
10722
|
}
|
|
@@ -14079,12 +14079,12 @@ var require_dist2 = __commonJS({
|
|
|
14079
14079
|
throw new Error(`Unknown format "${name}"`);
|
|
14080
14080
|
return f;
|
|
14081
14081
|
};
|
|
14082
|
-
function addFormats(ajv, list,
|
|
14082
|
+
function addFormats(ajv, list, fs5, exportName) {
|
|
14083
14083
|
var _a2;
|
|
14084
14084
|
var _b;
|
|
14085
14085
|
(_a2 = (_b = ajv.opts.code).formats) !== null && _a2 !== void 0 ? _a2 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
14086
14086
|
for (const f of list)
|
|
14087
|
-
ajv.addFormat(f,
|
|
14087
|
+
ajv.addFormat(f, fs5[f]);
|
|
14088
14088
|
}
|
|
14089
14089
|
module2.exports = exports2 = formatsPlugin;
|
|
14090
14090
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
@@ -14099,126 +14099,31 @@ __export(config_exports, {
|
|
|
14099
14099
|
loadSiteConfig: () => loadSiteConfig
|
|
14100
14100
|
});
|
|
14101
14101
|
function loadOmtConfig(basePath) {
|
|
14102
|
-
const configPath =
|
|
14102
|
+
const configPath = path4.join(basePath, "oh-my-til.json");
|
|
14103
14103
|
try {
|
|
14104
|
-
const raw =
|
|
14104
|
+
const raw = fs3.readFileSync(configPath, "utf-8");
|
|
14105
14105
|
const parsed = JSON.parse(raw);
|
|
14106
14106
|
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
|
|
14107
14107
|
} catch {
|
|
14108
14108
|
return {};
|
|
14109
14109
|
}
|
|
14110
14110
|
}
|
|
14111
|
-
var
|
|
14111
|
+
var path4, fs3, loadSiteConfig;
|
|
14112
14112
|
var init_config = __esm({
|
|
14113
14113
|
"src/core/config.ts"() {
|
|
14114
|
-
|
|
14115
|
-
|
|
14114
|
+
path4 = __toESM(require("path"));
|
|
14115
|
+
fs3 = __toESM(require("fs"));
|
|
14116
14116
|
loadSiteConfig = loadOmtConfig;
|
|
14117
14117
|
}
|
|
14118
14118
|
});
|
|
14119
14119
|
|
|
14120
|
-
// src/cli/global-config.ts
|
|
14121
|
-
var fs = __toESM(require("fs"));
|
|
14122
|
-
var os = __toESM(require("os"));
|
|
14123
|
-
var path = __toESM(require("path"));
|
|
14124
|
-
var CONFIG_DIR = path.join(os.homedir(), ".config", "oh-my-til");
|
|
14125
|
-
var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
14126
|
-
var VALID_PROVIDERS = ["anthropic", "openai", "ollama"];
|
|
14127
|
-
function readConfig() {
|
|
14128
|
-
if (!fs.existsSync(CONFIG_FILE))
|
|
14129
|
-
return {};
|
|
14130
|
-
try {
|
|
14131
|
-
const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
14132
|
-
return JSON.parse(raw);
|
|
14133
|
-
} catch {
|
|
14134
|
-
return {};
|
|
14135
|
-
}
|
|
14136
|
-
}
|
|
14137
|
-
function writeConfig(config2) {
|
|
14138
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
14139
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config2, null, 2), { encoding: "utf-8", mode: 384 });
|
|
14140
|
-
}
|
|
14141
|
-
function maskApiKey(key) {
|
|
14142
|
-
if (key.length <= 8)
|
|
14143
|
-
return "***";
|
|
14144
|
-
return key.slice(0, 4) + "..." + key.slice(-4);
|
|
14145
|
-
}
|
|
14146
|
-
function runConfigCommand(args) {
|
|
14147
|
-
const subcommand = args[0];
|
|
14148
|
-
if (!subcommand || subcommand === "get") {
|
|
14149
|
-
const config2 = readConfig();
|
|
14150
|
-
const display = {};
|
|
14151
|
-
if (config2.vault)
|
|
14152
|
-
display["vault"] = config2.vault;
|
|
14153
|
-
if (config2.ai) {
|
|
14154
|
-
display["ai"] = {
|
|
14155
|
-
provider: config2.ai.provider,
|
|
14156
|
-
...config2.ai.model ? { model: config2.ai.model } : {},
|
|
14157
|
-
...config2.ai.apiKey ? { apiKey: maskApiKey(config2.ai.apiKey) } : {}
|
|
14158
|
-
};
|
|
14159
|
-
}
|
|
14160
|
-
if (Object.keys(display).length === 0) {
|
|
14161
|
-
console.log("No config set. Use `omt config set <key> <value>`");
|
|
14162
|
-
} else {
|
|
14163
|
-
console.log(JSON.stringify(display, null, 2));
|
|
14164
|
-
}
|
|
14165
|
-
return;
|
|
14166
|
-
}
|
|
14167
|
-
if (subcommand === "set") {
|
|
14168
|
-
const key = args[1];
|
|
14169
|
-
const value = args[2];
|
|
14170
|
-
if (!key || value === void 0) {
|
|
14171
|
-
console.error("Usage: omt config set <key> <value>");
|
|
14172
|
-
console.error("Keys: vault, ai.provider, ai.model, ai.apiKey");
|
|
14173
|
-
process.exit(1);
|
|
14174
|
-
}
|
|
14175
|
-
const config2 = readConfig();
|
|
14176
|
-
if (key === "vault") {
|
|
14177
|
-
config2.vault = value;
|
|
14178
|
-
writeConfig(config2);
|
|
14179
|
-
console.log(`vault = ${value}`);
|
|
14180
|
-
} else if (key === "ai.provider") {
|
|
14181
|
-
if (!VALID_PROVIDERS.includes(value)) {
|
|
14182
|
-
console.error(`Invalid provider. Choose from: ${VALID_PROVIDERS.join(", ")}`);
|
|
14183
|
-
process.exit(1);
|
|
14184
|
-
}
|
|
14185
|
-
config2.ai = { ...config2.ai, provider: value };
|
|
14186
|
-
writeConfig(config2);
|
|
14187
|
-
console.log(`ai.provider = ${value}`);
|
|
14188
|
-
} else if (key === "ai.model") {
|
|
14189
|
-
if (!config2.ai) {
|
|
14190
|
-
console.error("Set ai.provider first.");
|
|
14191
|
-
process.exit(1);
|
|
14192
|
-
}
|
|
14193
|
-
config2.ai.model = value;
|
|
14194
|
-
writeConfig(config2);
|
|
14195
|
-
console.log(`ai.model = ${value}`);
|
|
14196
|
-
} else if (key === "ai.apiKey") {
|
|
14197
|
-
if (!config2.ai) {
|
|
14198
|
-
console.error("Set ai.provider first.");
|
|
14199
|
-
process.exit(1);
|
|
14200
|
-
}
|
|
14201
|
-
config2.ai.apiKey = value;
|
|
14202
|
-
writeConfig(config2);
|
|
14203
|
-
console.log(`ai.apiKey = ${maskApiKey(value)}`);
|
|
14204
|
-
} else {
|
|
14205
|
-
console.error(`Unknown key: ${key}`);
|
|
14206
|
-
console.error("Keys: vault, ai.provider, ai.model, ai.apiKey");
|
|
14207
|
-
process.exit(1);
|
|
14208
|
-
}
|
|
14209
|
-
return;
|
|
14210
|
-
}
|
|
14211
|
-
console.error(`Unknown config subcommand: ${subcommand}`);
|
|
14212
|
-
process.exit(1);
|
|
14213
|
-
}
|
|
14214
|
-
|
|
14215
14120
|
// src/adapters/fs-adapter.ts
|
|
14216
|
-
var
|
|
14217
|
-
var
|
|
14121
|
+
var fs = __toESM(require("fs/promises"));
|
|
14122
|
+
var path = __toESM(require("path"));
|
|
14218
14123
|
var import_yaml = __toESM(require_dist());
|
|
14219
14124
|
function resolveSafe(resolvedBase, filePath) {
|
|
14220
|
-
const resolved =
|
|
14221
|
-
if (resolved !== resolvedBase && !resolved.startsWith(resolvedBase +
|
|
14125
|
+
const resolved = path.resolve(resolvedBase, filePath);
|
|
14126
|
+
if (resolved !== resolvedBase && !resolved.startsWith(resolvedBase + path.sep)) {
|
|
14222
14127
|
throw new Error(`Path traversal denied: ${filePath}`);
|
|
14223
14128
|
}
|
|
14224
14129
|
return resolved;
|
|
@@ -14226,11 +14131,11 @@ function resolveSafe(resolvedBase, filePath) {
|
|
|
14226
14131
|
var FsStorage = class {
|
|
14227
14132
|
constructor(basePath) {
|
|
14228
14133
|
this.basePath = basePath;
|
|
14229
|
-
this.resolvedBase =
|
|
14134
|
+
this.resolvedBase = path.resolve(basePath);
|
|
14230
14135
|
}
|
|
14231
14136
|
async readFile(filePath) {
|
|
14232
14137
|
try {
|
|
14233
|
-
return await
|
|
14138
|
+
return await fs.readFile(resolveSafe(this.resolvedBase, filePath), "utf-8");
|
|
14234
14139
|
} catch {
|
|
14235
14140
|
return null;
|
|
14236
14141
|
}
|
|
@@ -14244,7 +14149,7 @@ var FsStorage = class {
|
|
|
14244
14149
|
const fullDir = resolveSafe(this.resolvedBase, dir || ".");
|
|
14245
14150
|
let dirents;
|
|
14246
14151
|
try {
|
|
14247
|
-
dirents = await
|
|
14152
|
+
dirents = await fs.readdir(fullDir, { withFileTypes: true });
|
|
14248
14153
|
} catch {
|
|
14249
14154
|
return;
|
|
14250
14155
|
}
|
|
@@ -14257,7 +14162,7 @@ var FsStorage = class {
|
|
|
14257
14162
|
} else if (d.isFile()) {
|
|
14258
14163
|
const ext = d.name.includes(".") ? d.name.split(".").pop() : "";
|
|
14259
14164
|
try {
|
|
14260
|
-
const stat2 = await
|
|
14165
|
+
const stat2 = await fs.stat(resolveSafe(this.resolvedBase, relative));
|
|
14261
14166
|
entries.push({
|
|
14262
14167
|
path: relative,
|
|
14263
14168
|
extension: ext,
|
|
@@ -14272,7 +14177,7 @@ var FsStorage = class {
|
|
|
14272
14177
|
}
|
|
14273
14178
|
async exists(filePath) {
|
|
14274
14179
|
try {
|
|
14275
|
-
await
|
|
14180
|
+
await fs.access(resolveSafe(this.resolvedBase, filePath));
|
|
14276
14181
|
return true;
|
|
14277
14182
|
} catch {
|
|
14278
14183
|
return false;
|
|
@@ -14280,15 +14185,15 @@ var FsStorage = class {
|
|
|
14280
14185
|
}
|
|
14281
14186
|
async writeFile(filePath, content) {
|
|
14282
14187
|
const fullPath = resolveSafe(this.resolvedBase, filePath);
|
|
14283
|
-
await
|
|
14284
|
-
await
|
|
14188
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
14189
|
+
await fs.writeFile(fullPath, content, "utf-8");
|
|
14285
14190
|
}
|
|
14286
14191
|
async mkdir(dirPath) {
|
|
14287
|
-
await
|
|
14192
|
+
await fs.mkdir(resolveSafe(this.resolvedBase, dirPath), { recursive: true });
|
|
14288
14193
|
}
|
|
14289
14194
|
async remove(filePath) {
|
|
14290
14195
|
try {
|
|
14291
|
-
await
|
|
14196
|
+
await fs.unlink(resolveSafe(this.resolvedBase, filePath));
|
|
14292
14197
|
} catch {
|
|
14293
14198
|
}
|
|
14294
14199
|
}
|
|
@@ -14301,13 +14206,13 @@ var FsMetadata = class {
|
|
|
14301
14206
|
constructor(basePath, storage) {
|
|
14302
14207
|
this.basePath = basePath;
|
|
14303
14208
|
this.linkScanCache = null;
|
|
14304
|
-
this.resolvedBase =
|
|
14209
|
+
this.resolvedBase = path.resolve(basePath);
|
|
14305
14210
|
this.storage = storage ?? new FsStorage(basePath);
|
|
14306
14211
|
}
|
|
14307
14212
|
async getFileMetadata(filePath) {
|
|
14308
14213
|
let content;
|
|
14309
14214
|
try {
|
|
14310
|
-
content = await
|
|
14215
|
+
content = await fs.readFile(resolveSafe(this.resolvedBase, filePath), "utf-8");
|
|
14311
14216
|
} catch {
|
|
14312
14217
|
return null;
|
|
14313
14218
|
}
|
|
@@ -14837,8 +14742,8 @@ function getErrorMap() {
|
|
|
14837
14742
|
|
|
14838
14743
|
// node_modules/zod/v3/helpers/parseUtil.js
|
|
14839
14744
|
var makeIssue = (params) => {
|
|
14840
|
-
const { data, path:
|
|
14841
|
-
const fullPath = [...
|
|
14745
|
+
const { data, path: path6, errorMaps, issueData } = params;
|
|
14746
|
+
const fullPath = [...path6, ...issueData.path || []];
|
|
14842
14747
|
const fullIssue = {
|
|
14843
14748
|
...issueData,
|
|
14844
14749
|
path: fullPath
|
|
@@ -14953,11 +14858,11 @@ var errorUtil;
|
|
|
14953
14858
|
|
|
14954
14859
|
// node_modules/zod/v3/types.js
|
|
14955
14860
|
var ParseInputLazyPath = class {
|
|
14956
|
-
constructor(parent, value,
|
|
14861
|
+
constructor(parent, value, path6, key) {
|
|
14957
14862
|
this._cachedPath = [];
|
|
14958
14863
|
this.parent = parent;
|
|
14959
14864
|
this.data = value;
|
|
14960
|
-
this._path =
|
|
14865
|
+
this._path = path6;
|
|
14961
14866
|
this._key = key;
|
|
14962
14867
|
}
|
|
14963
14868
|
get path() {
|
|
@@ -18881,10 +18786,10 @@ function mergeDefs(...defs) {
|
|
|
18881
18786
|
function cloneDef(schema) {
|
|
18882
18787
|
return mergeDefs(schema._zod.def);
|
|
18883
18788
|
}
|
|
18884
|
-
function getElementAtPath(obj,
|
|
18885
|
-
if (!
|
|
18789
|
+
function getElementAtPath(obj, path6) {
|
|
18790
|
+
if (!path6)
|
|
18886
18791
|
return obj;
|
|
18887
|
-
return
|
|
18792
|
+
return path6.reduce((acc, key) => acc?.[key], obj);
|
|
18888
18793
|
}
|
|
18889
18794
|
function promiseAllObject(promisesObj) {
|
|
18890
18795
|
const keys = Object.keys(promisesObj);
|
|
@@ -19267,11 +19172,11 @@ function aborted(x, startIndex = 0) {
|
|
|
19267
19172
|
}
|
|
19268
19173
|
return false;
|
|
19269
19174
|
}
|
|
19270
|
-
function prefixIssues(
|
|
19175
|
+
function prefixIssues(path6, issues) {
|
|
19271
19176
|
return issues.map((iss) => {
|
|
19272
19177
|
var _a2;
|
|
19273
19178
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
19274
|
-
iss.path.unshift(
|
|
19179
|
+
iss.path.unshift(path6);
|
|
19275
19180
|
return iss;
|
|
19276
19181
|
});
|
|
19277
19182
|
}
|
|
@@ -19454,7 +19359,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
|
|
|
19454
19359
|
}
|
|
19455
19360
|
function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
19456
19361
|
const result = { errors: [] };
|
|
19457
|
-
const processError = (error49,
|
|
19362
|
+
const processError = (error49, path6 = []) => {
|
|
19458
19363
|
var _a2, _b;
|
|
19459
19364
|
for (const issue2 of error49.issues) {
|
|
19460
19365
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -19464,7 +19369,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
19464
19369
|
} else if (issue2.code === "invalid_element") {
|
|
19465
19370
|
processError({ issues: issue2.issues }, issue2.path);
|
|
19466
19371
|
} else {
|
|
19467
|
-
const fullpath = [...
|
|
19372
|
+
const fullpath = [...path6, ...issue2.path];
|
|
19468
19373
|
if (fullpath.length === 0) {
|
|
19469
19374
|
result.errors.push(mapper(issue2));
|
|
19470
19375
|
continue;
|
|
@@ -19496,8 +19401,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
19496
19401
|
}
|
|
19497
19402
|
function toDotPath(_path) {
|
|
19498
19403
|
const segs = [];
|
|
19499
|
-
const
|
|
19500
|
-
for (const seg of
|
|
19404
|
+
const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
19405
|
+
for (const seg of path6) {
|
|
19501
19406
|
if (typeof seg === "number")
|
|
19502
19407
|
segs.push(`[${seg}]`);
|
|
19503
19408
|
else if (typeof seg === "symbol")
|
|
@@ -31902,13 +31807,13 @@ function resolveRef(ref, ctx) {
|
|
|
31902
31807
|
if (!ref.startsWith("#")) {
|
|
31903
31808
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
31904
31809
|
}
|
|
31905
|
-
const
|
|
31906
|
-
if (
|
|
31810
|
+
const path6 = ref.slice(1).split("/").filter(Boolean);
|
|
31811
|
+
if (path6.length === 0) {
|
|
31907
31812
|
return ctx.rootSchema;
|
|
31908
31813
|
}
|
|
31909
31814
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
31910
|
-
if (
|
|
31911
|
-
const key =
|
|
31815
|
+
if (path6[0] === defsKey) {
|
|
31816
|
+
const key = path6[1];
|
|
31912
31817
|
if (!key || !ctx.defs[key]) {
|
|
31913
31818
|
throw new Error(`Reference not found: ${ref}`);
|
|
31914
31819
|
}
|
|
@@ -37783,9 +37688,9 @@ function extractCategory(filePath, tilPath) {
|
|
|
37783
37688
|
const parts = relative.split("/");
|
|
37784
37689
|
return parts.length >= 2 ? parts[0] : "(uncategorized)";
|
|
37785
37690
|
}
|
|
37786
|
-
function buildFileContext(
|
|
37787
|
-
const category = extractCategory(
|
|
37788
|
-
return { path:
|
|
37691
|
+
function buildFileContext(path6, tilPath, matchType, headings, outgoingLinks, backlinks, tags) {
|
|
37692
|
+
const category = extractCategory(path6, tilPath);
|
|
37693
|
+
return { path: path6, category, headings, outgoingLinks, backlinks, tags, matchType };
|
|
37789
37694
|
}
|
|
37790
37695
|
function findUnresolvedMentions(unresolvedLinks, topic, tilPath) {
|
|
37791
37696
|
const lowerTopic = topic.toLowerCase();
|
|
@@ -37960,10 +37865,10 @@ function parseBacklogSections(content) {
|
|
|
37960
37865
|
if (itemMatch) {
|
|
37961
37866
|
const done = itemMatch[1] !== " ";
|
|
37962
37867
|
const rawPath = itemMatch[3].trim();
|
|
37963
|
-
const
|
|
37964
|
-
const displayName = itemMatch[2]?.trim() ||
|
|
37965
|
-
const slug =
|
|
37966
|
-
const item = { displayName, path:
|
|
37868
|
+
const path6 = rawPath.endsWith(".md") ? rawPath : rawPath + ".md";
|
|
37869
|
+
const displayName = itemMatch[2]?.trim() || path6.replace(/\.md$/, "");
|
|
37870
|
+
const slug = path6.replace(/\.md$/, "").split("/").pop() ?? "";
|
|
37871
|
+
const item = { displayName, path: path6, done };
|
|
37967
37872
|
if (sources[slug] && sources[slug].length > 0) {
|
|
37968
37873
|
item.sourceUrls = sources[slug];
|
|
37969
37874
|
}
|
|
@@ -38583,7 +38488,7 @@ function registerTools(server, storage, metadata, tilPath) {
|
|
|
38583
38488
|
const texts = await Promise.all(batch.map((f) => storage.readFile(f.path)));
|
|
38584
38489
|
for (let j = 0; j < batch.length; j++) {
|
|
38585
38490
|
const text = texts[j];
|
|
38586
|
-
if (text
|
|
38491
|
+
if (text !== null && text.toLowerCase().includes(lowerTopic)) {
|
|
38587
38492
|
contentMatches.push(batch[j].path);
|
|
38588
38493
|
}
|
|
38589
38494
|
if (pathMatches.length + contentMatches.length >= 20)
|
|
@@ -38773,31 +38678,31 @@ function registerTools(server, storage, metadata, tilPath) {
|
|
|
38773
38678
|
action: external_exports3.enum(["review", "remove"]).optional().describe("review (default): record review, remove: remove from review schedule")
|
|
38774
38679
|
})
|
|
38775
38680
|
},
|
|
38776
|
-
async ({ path:
|
|
38681
|
+
async ({ path: path6, grade, action }) => {
|
|
38777
38682
|
const effectiveAction = action ?? "review";
|
|
38778
|
-
const content = await storage.readFile(
|
|
38683
|
+
const content = await storage.readFile(path6);
|
|
38779
38684
|
if (content === null) {
|
|
38780
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: `File not found: ${
|
|
38685
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `File not found: ${path6}` }) }], isError: true };
|
|
38781
38686
|
}
|
|
38782
38687
|
if (effectiveAction === "remove") {
|
|
38783
38688
|
const updated2 = removeFrontmatterSrs(content);
|
|
38784
|
-
await storage.writeFile(
|
|
38785
|
-
return { content: [{ type: "text", text: JSON.stringify({ path:
|
|
38689
|
+
await storage.writeFile(path6, updated2);
|
|
38690
|
+
return { content: [{ type: "text", text: JSON.stringify({ path: path6, removed: true }) }] };
|
|
38786
38691
|
}
|
|
38787
38692
|
if (grade === void 0) {
|
|
38788
38693
|
return { content: [{ type: "text", text: "Error: grade (0-5) is required when action=review" }], isError: true };
|
|
38789
38694
|
}
|
|
38790
|
-
const fileMeta = await metadata.getFileMetadata(
|
|
38695
|
+
const fileMeta = await metadata.getFileMetadata(path6);
|
|
38791
38696
|
const fm = fileMeta?.frontmatter ?? {};
|
|
38792
38697
|
const currentSrs = parseSrsMetadata(fm) ?? createDefaultSrsMetadata();
|
|
38793
38698
|
const newSrs = computeNextReview(currentSrs, grade);
|
|
38794
38699
|
const updated = updateFrontmatterSrs(content, newSrs);
|
|
38795
|
-
await storage.writeFile(
|
|
38700
|
+
await storage.writeFile(path6, updated);
|
|
38796
38701
|
return {
|
|
38797
38702
|
content: [{
|
|
38798
38703
|
type: "text",
|
|
38799
38704
|
text: JSON.stringify({
|
|
38800
|
-
path:
|
|
38705
|
+
path: path6,
|
|
38801
38706
|
grade,
|
|
38802
38707
|
next_review: newSrs.next_review,
|
|
38803
38708
|
interval: newSrs.interval,
|
|
@@ -38826,7 +38731,7 @@ function registerTools(server, storage, metadata, tilPath) {
|
|
|
38826
38731
|
})
|
|
38827
38732
|
},
|
|
38828
38733
|
async ({ category, slug, title, content, tags, date: date5, fmCategory, aliases, auto_check_backlog }) => {
|
|
38829
|
-
const
|
|
38734
|
+
const path6 = `${tilPath}/${category}/${slug}.md`;
|
|
38830
38735
|
const noteDate = date5 || (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
38831
38736
|
const fmLines = ["---", `title: "${title.replace(/"/g, '\\"')}"`, `date: ${noteDate}`];
|
|
38832
38737
|
const effectiveCategory = fmCategory ?? category;
|
|
@@ -38843,9 +38748,9 @@ function registerTools(server, storage, metadata, tilPath) {
|
|
|
38843
38748
|
fmLines.push("---", "");
|
|
38844
38749
|
const fullContent = fmLines.join("\n") + content;
|
|
38845
38750
|
await storage.mkdir(`${tilPath}/${category}`);
|
|
38846
|
-
const existed = await storage.exists(
|
|
38847
|
-
await storage.writeFile(
|
|
38848
|
-
const data = { path:
|
|
38751
|
+
const existed = await storage.exists(path6);
|
|
38752
|
+
await storage.writeFile(path6, fullContent);
|
|
38753
|
+
const data = { path: path6, created: !existed, category, slug, title };
|
|
38849
38754
|
if (auto_check_backlog) {
|
|
38850
38755
|
const backlogPath = `${tilPath}/${category}/backlog.md`;
|
|
38851
38756
|
const backlogContent = await storage.readFile(backlogPath);
|
|
@@ -38892,13 +38797,13 @@ function registerTools(server, storage, metadata, tilPath) {
|
|
|
38892
38797
|
}
|
|
38893
38798
|
|
|
38894
38799
|
// src/cli/obsidian-install.ts
|
|
38895
|
-
var
|
|
38896
|
-
var
|
|
38800
|
+
var path2 = __toESM(require("path"));
|
|
38801
|
+
var fs2 = __toESM(require("fs"));
|
|
38897
38802
|
var import_child_process = require("child_process");
|
|
38898
38803
|
var PLUGIN_ARTIFACTS = ["main.js", "manifest.json", "styles.css", "migrate-links.mjs"];
|
|
38899
38804
|
var VERSION_PATTERN = /^\d+\.\d+\.\d+(-\S+)?$/;
|
|
38900
38805
|
function getPluginArtifacts(packageRoot) {
|
|
38901
|
-
return PLUGIN_ARTIFACTS.map((f) =>
|
|
38806
|
+
return PLUGIN_ARTIFACTS.map((f) => path2.join(packageRoot, f));
|
|
38902
38807
|
}
|
|
38903
38808
|
function isValidVersion(v) {
|
|
38904
38809
|
return VERSION_PATTERN.test(v);
|
|
@@ -38934,7 +38839,7 @@ function detectElectronVersion(override) {
|
|
|
38934
38839
|
if (envVersion && isValidVersion(envVersion))
|
|
38935
38840
|
return envVersion;
|
|
38936
38841
|
const plist = "/Applications/Obsidian.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Resources/Info.plist";
|
|
38937
|
-
if (
|
|
38842
|
+
if (fs2.existsSync(plist)) {
|
|
38938
38843
|
try {
|
|
38939
38844
|
const output = (0, import_child_process.execFileSync)(
|
|
38940
38845
|
"/usr/libexec/PlistBuddy",
|
|
@@ -38949,12 +38854,12 @@ function detectElectronVersion(override) {
|
|
|
38949
38854
|
return null;
|
|
38950
38855
|
}
|
|
38951
38856
|
function installObsidianPlugin(vaultPath, packageRoot, options) {
|
|
38952
|
-
const pluginDir =
|
|
38857
|
+
const pluginDir = path2.join(vaultPath, ".obsidian", "plugins", "oh-my-til");
|
|
38953
38858
|
const warnings = [];
|
|
38954
38859
|
let rebuilt = false;
|
|
38955
38860
|
const artifacts = getPluginArtifacts(packageRoot);
|
|
38956
38861
|
for (const artifact of artifacts) {
|
|
38957
|
-
if (!
|
|
38862
|
+
if (!fs2.existsSync(artifact)) {
|
|
38958
38863
|
return {
|
|
38959
38864
|
success: false,
|
|
38960
38865
|
pluginDir,
|
|
@@ -38963,14 +38868,14 @@ function installObsidianPlugin(vaultPath, packageRoot, options) {
|
|
|
38963
38868
|
};
|
|
38964
38869
|
}
|
|
38965
38870
|
}
|
|
38966
|
-
|
|
38871
|
+
fs2.mkdirSync(pluginDir, { recursive: true });
|
|
38967
38872
|
for (const artifact of artifacts) {
|
|
38968
|
-
const dest =
|
|
38969
|
-
|
|
38873
|
+
const dest = path2.join(pluginDir, path2.basename(artifact));
|
|
38874
|
+
fs2.copyFileSync(artifact, dest);
|
|
38970
38875
|
}
|
|
38971
|
-
const pkgJsonPath =
|
|
38972
|
-
if (!
|
|
38973
|
-
|
|
38876
|
+
const pkgJsonPath = path2.join(pluginDir, "package.json");
|
|
38877
|
+
if (!fs2.existsSync(pkgJsonPath)) {
|
|
38878
|
+
fs2.writeFileSync(pkgJsonPath, buildPluginPackageJson());
|
|
38974
38879
|
}
|
|
38975
38880
|
try {
|
|
38976
38881
|
(0, import_child_process.execFileSync)("npm", ["install", "--omit=dev"], {
|
|
@@ -38993,19 +38898,19 @@ function installObsidianPlugin(vaultPath, packageRoot, options) {
|
|
|
38993
38898
|
);
|
|
38994
38899
|
return { success: true, pluginDir, warnings, rebuilt: false };
|
|
38995
38900
|
}
|
|
38996
|
-
const versionFile =
|
|
38997
|
-
const cachedVersion =
|
|
38901
|
+
const versionFile = path2.join(pluginDir, ".electron-version");
|
|
38902
|
+
const cachedVersion = fs2.existsSync(versionFile) ? fs2.readFileSync(versionFile, "utf-8").trim() : null;
|
|
38998
38903
|
if (!needsRebuild(electronVersion, cachedVersion)) {
|
|
38999
38904
|
return { success: true, pluginDir, warnings, rebuilt: false };
|
|
39000
38905
|
}
|
|
39001
38906
|
try {
|
|
39002
|
-
const ptyModulePath =
|
|
38907
|
+
const ptyModulePath = path2.join(pluginDir, "node_modules", "node-pty");
|
|
39003
38908
|
(0, import_child_process.execFileSync)("npx", ["@electron/rebuild", "-m", ptyModulePath, "-v", electronVersion], {
|
|
39004
38909
|
cwd: pluginDir,
|
|
39005
38910
|
stdio: ["pipe", "pipe", "pipe"],
|
|
39006
38911
|
encoding: "utf-8"
|
|
39007
38912
|
});
|
|
39008
|
-
|
|
38913
|
+
fs2.writeFileSync(versionFile, electronVersion);
|
|
39009
38914
|
rebuilt = true;
|
|
39010
38915
|
} catch (err) {
|
|
39011
38916
|
warnings.push(
|
|
@@ -39018,8 +38923,8 @@ function installObsidianPlugin(vaultPath, packageRoot, options) {
|
|
|
39018
38923
|
}
|
|
39019
38924
|
|
|
39020
38925
|
// src/core/cli.ts
|
|
39021
|
-
var
|
|
39022
|
-
var
|
|
38926
|
+
var path3 = __toESM(require("path"));
|
|
38927
|
+
var os = __toESM(require("os"));
|
|
39023
38928
|
var VALUE_OPTIONS = /* @__PURE__ */ new Set(["port", "til-path", "out", "title", "subtitle", "github", "mode"]);
|
|
39024
38929
|
var BOOLEAN_OPTIONS = /* @__PURE__ */ new Set(["no-obsidian"]);
|
|
39025
38930
|
function parseArgs(args) {
|
|
@@ -39039,7 +38944,7 @@ function parseArgs(args) {
|
|
|
39039
38944
|
}
|
|
39040
38945
|
function expandTilde(p) {
|
|
39041
38946
|
if (p === "~" || p.startsWith("~/")) {
|
|
39042
|
-
return
|
|
38947
|
+
return path3.join(os.homedir(), p.slice(1));
|
|
39043
38948
|
}
|
|
39044
38949
|
return p;
|
|
39045
38950
|
}
|
|
@@ -40036,9 +39941,9 @@ function generateProfileHtml(config2, summaryCardsHtml, heatmapHtml, recentTilsH
|
|
|
40036
39941
|
}
|
|
40037
39942
|
|
|
40038
39943
|
// src/cli/index.ts
|
|
40039
|
-
var
|
|
40040
|
-
var
|
|
40041
|
-
var VERSION = true ? "1.
|
|
39944
|
+
var path5 = __toESM(require("path"));
|
|
39945
|
+
var fs4 = __toESM(require("fs"));
|
|
39946
|
+
var VERSION = true ? "1.4.0" : "0.0.0";
|
|
40042
39947
|
function printUsage() {
|
|
40043
39948
|
console.log(`oh-my-til v${VERSION}
|
|
40044
39949
|
|
|
@@ -40046,16 +39951,8 @@ Usage:
|
|
|
40046
39951
|
oh-my-til mcp [<path>] [options] Start MCP server (stdio)
|
|
40047
39952
|
oh-my-til install-obsidian [<path>] Install Obsidian desktop plugin only
|
|
40048
39953
|
oh-my-til deploy [<path>] [options] Generate static site from TIL files
|
|
40049
|
-
oh-my-til config get Show current global config
|
|
40050
|
-
oh-my-til config set <key> <value> Set a config value
|
|
40051
39954
|
oh-my-til version Print version
|
|
40052
39955
|
|
|
40053
|
-
Config keys:
|
|
40054
|
-
vault TIL vault path (e.g. ~/my-til)
|
|
40055
|
-
ai.provider AI provider: anthropic | openai | ollama
|
|
40056
|
-
ai.model AI model name
|
|
40057
|
-
ai.apiKey AI API key (stored with chmod 600)
|
|
40058
|
-
|
|
40059
39956
|
Options (mcp):
|
|
40060
39957
|
--til-path <path> TIL folder path (default: til)
|
|
40061
39958
|
|
|
@@ -40089,7 +39986,7 @@ async function main() {
|
|
|
40089
39986
|
const parsed = parseArgs(args.slice(1));
|
|
40090
39987
|
const rawPath = parsed.positional[0];
|
|
40091
39988
|
const envPath = process.env["TIL_VAULT_PATH"];
|
|
40092
|
-
const basePath =
|
|
39989
|
+
const basePath = path5.resolve(
|
|
40093
39990
|
rawPath ? expandTilde(rawPath) : envPath ? expandTilde(envPath) : process.cwd()
|
|
40094
39991
|
);
|
|
40095
39992
|
const tilPath = parsed.options["til-path"] ?? "til";
|
|
@@ -40112,13 +40009,13 @@ async function main() {
|
|
|
40112
40009
|
process.on("SIGINT", shutdown);
|
|
40113
40010
|
process.on("SIGTERM", shutdown);
|
|
40114
40011
|
} else if (command === "install-obsidian") {
|
|
40115
|
-
const obsidianDir =
|
|
40116
|
-
if (!
|
|
40012
|
+
const obsidianDir = path5.join(basePath, ".obsidian");
|
|
40013
|
+
if (!fs4.existsSync(obsidianDir)) {
|
|
40117
40014
|
console.error("No .obsidian/ folder found. Run this command inside an Obsidian vault.");
|
|
40118
40015
|
process.exit(1);
|
|
40119
40016
|
}
|
|
40120
40017
|
console.log("Installing Obsidian plugin...");
|
|
40121
|
-
const packageRoot =
|
|
40018
|
+
const packageRoot = path5.resolve(__dirname, "..");
|
|
40122
40019
|
const result = installObsidianPlugin(basePath, packageRoot);
|
|
40123
40020
|
if (result.success) {
|
|
40124
40021
|
console.log(`Plugin installed: ${result.pluginDir}`);
|
|
@@ -40212,13 +40109,13 @@ async function main() {
|
|
|
40212
40109
|
{ title: entry.title, category: entry.category, createdDate: entry.createdDate, contentHtml: entry.contentHtml, relatedTils },
|
|
40213
40110
|
config2
|
|
40214
40111
|
);
|
|
40215
|
-
await storage.writeFile(
|
|
40112
|
+
await storage.writeFile(path5.join(outDir, entry.category, `${entry.slug}.html`), tilPageHtml);
|
|
40216
40113
|
generated++;
|
|
40217
40114
|
}
|
|
40218
40115
|
for (const [category, tils] of categoryMap) {
|
|
40219
40116
|
tils.sort((a, b) => b.createdDate.localeCompare(a.createdDate));
|
|
40220
40117
|
const catHtml = generateCategoryIndexHtml({ category, tils }, config2);
|
|
40221
|
-
await storage.writeFile(
|
|
40118
|
+
await storage.writeFile(path5.join(outDir, category, "index.html"), catHtml);
|
|
40222
40119
|
}
|
|
40223
40120
|
const heatmapData = computeHeatmapData(statsEntries, deployTilPath, void 0, statsEntries);
|
|
40224
40121
|
const streak = computeStreak(statsEntries, deployTilPath, void 0, statsEntries);
|
|
@@ -40244,11 +40141,9 @@ async function main() {
|
|
|
40244
40141
|
const recentTilsHtml = renderRecentTilsHtml(recentTils);
|
|
40245
40142
|
const allTilsHtml = renderAllTilsHtml(allCategories);
|
|
40246
40143
|
const profileHtml = generateProfileHtml(config2, summaryCardsHtml, heatmapHtml, recentTilsHtml, allTilsHtml);
|
|
40247
|
-
await storage.writeFile(
|
|
40144
|
+
await storage.writeFile(path5.join(outDir, "index.html"), profileHtml);
|
|
40248
40145
|
console.log(`Generated ${generated} TIL pages + ${categoryMap.size} category indexes + profile`);
|
|
40249
|
-
console.log(`Output: ${
|
|
40250
|
-
} else if (command === "config") {
|
|
40251
|
-
runConfigCommand(args.slice(1));
|
|
40146
|
+
console.log(`Output: ${path5.resolve(basePath, outDir)}/`);
|
|
40252
40147
|
} else {
|
|
40253
40148
|
console.error(`Unknown command: ${command}`);
|
|
40254
40149
|
printUsage();
|
package/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-til",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Oh My TIL
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "Oh My TIL \u2014 Claude Code plugin for TIL learning workflow, with Obsidian integration",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"oh-my-til": "./dist/cli.js"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"build": "node esbuild.config.mjs production && node esbuild.cli.mjs",
|
|
19
19
|
"build:obsidian": "node esbuild.config.mjs production",
|
|
20
20
|
"build:cli": "node esbuild.cli.mjs",
|
|
21
|
-
"rebuild-pty": "npx @electron/rebuild -m node_modules/node-pty -v ${ELECTRON_VERSION:?'ELECTRON_VERSION
|
|
21
|
+
"rebuild-pty": "npx @electron/rebuild -m node_modules/node-pty -v ${ELECTRON_VERSION:?'ELECTRON_VERSION \ud658\uacbd\ubcc0\uc218\ub97c \uc124\uc815\ud558\uc138\uc694 (\uc608: ELECTRON_VERSION=37.10.2 npm run rebuild-pty)'}",
|
|
22
22
|
"deploy": "bash scripts/deploy.sh",
|
|
23
23
|
"check": "vitest run && node esbuild.config.mjs production && tsc --noEmit",
|
|
24
24
|
"test": "vitest run",
|