@gobi-starweaver/ws-cli 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{chunks/chunk-S7EGXUII.js → dist/chunks/chunk-B2GIIUZ7.js} +2 -2
- package/{chunks/chunk-O2SA6W2I.js → dist/chunks/chunk-EMOHUSKW.js} +820 -456
- package/{chunks/chunk-BUBNMVFA.js → dist/chunks/chunk-PJQH2TDX.js} +2 -2
- package/{chunks/devtools-V5WI5QLO.js → dist/chunks/devtools-6ASGFZ3B.js} +33 -33
- package/{cli.js → dist/cli.js} +3988 -1199
- package/{mcp → dist/mcp}/servers/doc-toolchain-server.js +8 -8
- package/{mcp → dist/mcp}/servers/ws-vision-ocr-server.js +2 -2
- package/dist/vendor/hj_corpus/hj_corpus_mcp_server.py +436 -0
- package/dist/vendor/hj_corpus/hj_index_search.py +359 -0
- package/package.json +31 -11
- package/README-en.md +0 -52
- /package/{assets → dist/assets}/assembly-orders/README.md +0 -0
- /package/{assets → dist/assets}/asset-catalog/index.json +0 -0
- /package/{assets → dist/assets}/asset-catalog/snapshots/catalog-2026-08-15.json +0 -0
- /package/{assets → dist/assets}/card-templates/README.md +0 -0
- /package/{assets → dist/assets}/card-templates/collator.card.json +0 -0
- /package/{assets → dist/assets}/card-templates/deep-reader.card.json +0 -0
- /package/{assets → dist/assets}/card-templates/grant-editor.card.json +0 -0
- /package/{assets → dist/assets}/card-templates/grant-writer.card.json +0 -0
- /package/{assets → dist/assets}/card-templates/paper-review-rechecker.card.json +0 -0
- /package/{assets → dist/assets}/card-templates/paper-reviewer.card.json +0 -0
- /package/{assets → dist/assets}/card-templates/translation-reviewer.card.json +0 -0
- /package/{assets → dist/assets}/card-templates/translator.card.json +0 -0
- /package/{assets → dist/assets}/card-templates/visual-general.card.json +0 -0
- /package/{assets → dist/assets}/card-templates/writing-author.card.json +0 -0
- /package/{assets → dist/assets}/card-templates/writing-editor.card.json +0 -0
- /package/{assets → dist/assets}/card-templates//346/234/233/350/210/222/344/270/223/345/277/203/351/251/276/351/251/266/350/257/264/346/230/216_/345/206/231/344/275/234/347/273/204.md" +0 -0
- /package/{assets → dist/assets}/card-templates//346/234/233/350/210/222/344/270/223/345/277/203/351/251/276/351/251/266/350/257/264/346/230/216_/345/256/241/347/250/277/347/273/204.md" +0 -0
- /package/{assets → dist/assets}/card-templates//346/234/233/350/210/222/344/270/223/345/277/203/351/251/276/351/251/266/350/257/264/346/230/216_/347/224/263/346/212/245/347/273/204.md" +0 -0
- /package/{assets → dist/assets}/card-templates//346/234/233/350/210/222/344/270/223/345/277/203/351/251/276/351/251/266/350/257/264/346/230/216_/347/277/273/350/257/221/347/273/204.md" +0 -0
- /package/{assets → dist/assets}/card-templates//346/234/233/350/210/222/350/275/254/345/221/210/345/275/242/346/200/201/350/247/204/350/214/203.md" +0 -0
- /package/{assets → dist/assets}/expressions/ascii-art.json +0 -0
- /package/{assets → dist/assets}/expressions/emoji-full.json +0 -0
- /package/{assets → dist/assets}/expressions/emoji.json +0 -0
- /package/{bundled → dist/bundled}/plan/SKILL.md +0 -0
- /package/{templates → dist/templates}/prompts/init_command.md.ejs +0 -0
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import sqlite3
|
|
7
|
+
import time
|
|
8
|
+
import unicodedata
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
STATION_ROOT = Path(__file__).resolve().parents[2]
|
|
15
|
+
LOG_DIR = STATION_ROOT / "08_运行日志与资产清单"
|
|
16
|
+
# T5:hj 走 HJ_CORPUS_DB env(T4 透传桥·main.cjs resolveHjCorpusDb() 三级解析回写 process.env)。
|
|
17
|
+
# 本 DEFAULT_DB 仅 fallback·封装态不触(hj server 读 env 入口·封装态指向 resources/data/hj_4_jt_core_fts.sqlite)。
|
|
18
|
+
# 开发态 marker 在 → env 未设时 fallback 到站根/05_...(逐字保值)。
|
|
19
|
+
DEFAULT_DB = STATION_ROOT / "05_本地资料索引" / "translate" / "corpus" / "indexes" / "hj_4_jt_fts.sqlite"
|
|
20
|
+
DEFAULT_JSON = LOG_DIR / "HJ古籍索引只读接口验证_20260609.json"
|
|
21
|
+
DEFAULT_TXT = LOG_DIR / "HJ古籍索引只读接口验证_20260609.txt"
|
|
22
|
+
|
|
23
|
+
SPACE_RE = re.compile(r"\s+")
|
|
24
|
+
|
|
25
|
+
EVIDENCE_POLICY = {
|
|
26
|
+
"evidence_role": "candidate_source_and_context_only",
|
|
27
|
+
"evidence_note": "本地古籍 FTS 命中只作出处候选和上下文依据,不作页码依据。",
|
|
28
|
+
"page_basis": False,
|
|
29
|
+
"page_warning": "页码、版本、卷次细目仍须回到拟引用版本或权威数据库逐项核对。",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def now_iso() -> str:
|
|
34
|
+
return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def compact_query(query: str) -> str:
|
|
38
|
+
return SPACE_RE.sub(" ", query.strip())
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def normalize_with_map(text: str) -> tuple[str, list[int]]:
|
|
42
|
+
chars: list[str] = []
|
|
43
|
+
offsets: list[int] = []
|
|
44
|
+
for idx, ch in enumerate(text):
|
|
45
|
+
normalized = unicodedata.normalize("NFKC", ch)
|
|
46
|
+
for item in normalized:
|
|
47
|
+
category = unicodedata.category(item)
|
|
48
|
+
if category[0] in {"C", "P", "S", "Z"}:
|
|
49
|
+
continue
|
|
50
|
+
chars.append(item.lower())
|
|
51
|
+
offsets.append(idx)
|
|
52
|
+
return "".join(chars), offsets
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def normalize_query(query: str) -> str:
|
|
56
|
+
normalized, _ = normalize_with_map(query)
|
|
57
|
+
return normalized
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def fts_phrase(query: str) -> str:
|
|
61
|
+
return '"' + query.replace('"', '""') + '"'
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def open_hj_index(db_path: Path = DEFAULT_DB) -> sqlite3.Connection:
|
|
65
|
+
if not db_path.exists():
|
|
66
|
+
raise FileNotFoundError(f"HJ index not found: {db_path}")
|
|
67
|
+
conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
|
|
68
|
+
conn.row_factory = sqlite3.Row
|
|
69
|
+
conn.execute("PRAGMA query_only=ON")
|
|
70
|
+
conn.execute("PRAGMA temp_store=MEMORY")
|
|
71
|
+
conn.execute("PRAGMA cache_size=-262144")
|
|
72
|
+
return conn
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def inspect_hj_index(db_path: Path = DEFAULT_DB) -> dict[str, Any]:
|
|
76
|
+
conn = open_hj_index(db_path)
|
|
77
|
+
try:
|
|
78
|
+
metadata = {
|
|
79
|
+
row["key"]: row["value"]
|
|
80
|
+
for row in conn.execute("SELECT key, value FROM metadata ORDER BY key")
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
"db_path": str(db_path),
|
|
84
|
+
"opened_read_only": True,
|
|
85
|
+
"query_only": int(conn.execute("PRAGMA query_only").fetchone()[0]),
|
|
86
|
+
"doc_count": int(conn.execute("SELECT COUNT(*) FROM docs").fetchone()[0]),
|
|
87
|
+
"metadata": metadata,
|
|
88
|
+
"evidence_policy": EVIDENCE_POLICY,
|
|
89
|
+
}
|
|
90
|
+
finally:
|
|
91
|
+
conn.close()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def search_fts(
|
|
95
|
+
conn: sqlite3.Connection,
|
|
96
|
+
normalized_query: str,
|
|
97
|
+
limit: int,
|
|
98
|
+
book_prefix: str | None = None,
|
|
99
|
+
) -> list[sqlite3.Row]:
|
|
100
|
+
params: list[object] = [fts_phrase(normalized_query)]
|
|
101
|
+
where = ["docs_fts MATCH ?"]
|
|
102
|
+
if book_prefix:
|
|
103
|
+
where.append("d.book_base LIKE ?")
|
|
104
|
+
params.append(book_prefix.rstrip("/\\") + "%")
|
|
105
|
+
params.append(limit)
|
|
106
|
+
sql = f"""
|
|
107
|
+
SELECT
|
|
108
|
+
d.docid, d.rel_path, d.book_base, d.article_no,
|
|
109
|
+
d.title, d.source, d.body, d.char_count,
|
|
110
|
+
bm25(docs_fts) AS rank
|
|
111
|
+
FROM docs_fts
|
|
112
|
+
JOIN docs AS d ON d.docid = docs_fts.rowid
|
|
113
|
+
WHERE {' AND '.join(where)}
|
|
114
|
+
ORDER BY rank
|
|
115
|
+
LIMIT ?
|
|
116
|
+
"""
|
|
117
|
+
return list(conn.execute(sql, params))
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def search_like(
|
|
121
|
+
conn: sqlite3.Connection,
|
|
122
|
+
query: str,
|
|
123
|
+
limit: int,
|
|
124
|
+
book_prefix: str | None = None,
|
|
125
|
+
) -> list[sqlite3.Row]:
|
|
126
|
+
pattern = f"%{query}%"
|
|
127
|
+
params: list[object] = [pattern, pattern, pattern]
|
|
128
|
+
where = ["(d.title LIKE ? OR d.source LIKE ? OR d.body LIKE ?)"]
|
|
129
|
+
if book_prefix:
|
|
130
|
+
where.append("d.book_base LIKE ?")
|
|
131
|
+
params.append(book_prefix.rstrip("/\\") + "%")
|
|
132
|
+
params.append(limit)
|
|
133
|
+
sql = f"""
|
|
134
|
+
SELECT
|
|
135
|
+
d.docid, d.rel_path, d.book_base, d.article_no,
|
|
136
|
+
d.title, d.source, d.body, d.char_count,
|
|
137
|
+
0.0 AS rank
|
|
138
|
+
FROM docs AS d
|
|
139
|
+
WHERE {' AND '.join(where)}
|
|
140
|
+
LIMIT ?
|
|
141
|
+
"""
|
|
142
|
+
return list(conn.execute(sql, params))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def make_context(
|
|
146
|
+
text: str,
|
|
147
|
+
raw_query: str,
|
|
148
|
+
normalized_query: str,
|
|
149
|
+
width: int,
|
|
150
|
+
) -> tuple[str, int, str]:
|
|
151
|
+
pos = text.find(raw_query)
|
|
152
|
+
end_pos = pos + len(raw_query) if pos >= 0 else -1
|
|
153
|
+
if pos < 0:
|
|
154
|
+
normalized_text, offsets = normalize_with_map(text)
|
|
155
|
+
folded_pos = normalized_text.find(normalized_query)
|
|
156
|
+
if folded_pos >= 0 and folded_pos < len(offsets):
|
|
157
|
+
pos = offsets[folded_pos]
|
|
158
|
+
last = min(folded_pos + len(normalized_query) - 1, len(offsets) - 1)
|
|
159
|
+
end_pos = offsets[last] + 1
|
|
160
|
+
if pos < 0 or end_pos < 0:
|
|
161
|
+
return text[: width * 2].replace("\n", " "), -1, ""
|
|
162
|
+
start = max(0, pos - width)
|
|
163
|
+
end = min(len(text), end_pos + width)
|
|
164
|
+
prefix = "..." if start > 0 else ""
|
|
165
|
+
suffix = "..." if end < len(text) else ""
|
|
166
|
+
return (prefix + text[start:end] + suffix).replace("\n", " "), pos, text[pos:end_pos]
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def row_to_candidate(
|
|
170
|
+
row: sqlite3.Row,
|
|
171
|
+
raw_query: str,
|
|
172
|
+
normalized_query: str,
|
|
173
|
+
context_chars: int,
|
|
174
|
+
) -> dict[str, Any]:
|
|
175
|
+
best_context = ""
|
|
176
|
+
position = -1
|
|
177
|
+
matched_text = ""
|
|
178
|
+
hit_column = "body"
|
|
179
|
+
for name in ("title", "source", "body"):
|
|
180
|
+
context, pos, match = make_context(row[name] or "", raw_query, normalized_query, context_chars)
|
|
181
|
+
if pos >= 0:
|
|
182
|
+
best_context = context
|
|
183
|
+
position = pos
|
|
184
|
+
matched_text = match
|
|
185
|
+
hit_column = name
|
|
186
|
+
break
|
|
187
|
+
if not best_context:
|
|
188
|
+
best_context = context
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
"docid": row["docid"],
|
|
192
|
+
"book_base": row["book_base"],
|
|
193
|
+
"rel_path": row["rel_path"],
|
|
194
|
+
"article_no": row["article_no"],
|
|
195
|
+
"title": row["title"],
|
|
196
|
+
"source": row["source"],
|
|
197
|
+
"char_count": row["char_count"],
|
|
198
|
+
"rank": row["rank"],
|
|
199
|
+
"hit_column": hit_column,
|
|
200
|
+
"position": position,
|
|
201
|
+
"matched_text": matched_text,
|
|
202
|
+
"context": best_context,
|
|
203
|
+
"candidate_source": {
|
|
204
|
+
"book_base": row["book_base"],
|
|
205
|
+
"title": row["title"],
|
|
206
|
+
"article_no": row["article_no"],
|
|
207
|
+
"rel_path": row["rel_path"],
|
|
208
|
+
},
|
|
209
|
+
"evidence_role": EVIDENCE_POLICY["evidence_role"],
|
|
210
|
+
"page_basis": False,
|
|
211
|
+
"page_warning": EVIDENCE_POLICY["page_warning"],
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def search_hj_index(
|
|
216
|
+
query: str,
|
|
217
|
+
*,
|
|
218
|
+
db_path: Path = DEFAULT_DB,
|
|
219
|
+
limit: int = 10,
|
|
220
|
+
context_chars: int = 80,
|
|
221
|
+
book_prefix: str | None = None,
|
|
222
|
+
allow_short_scan: bool = False,
|
|
223
|
+
) -> dict[str, Any]:
|
|
224
|
+
raw_query = compact_query(query)
|
|
225
|
+
normalized_query = normalize_query(raw_query)
|
|
226
|
+
if not normalized_query:
|
|
227
|
+
raise ValueError("query is empty after normalization")
|
|
228
|
+
if limit < 1:
|
|
229
|
+
raise ValueError("limit must be positive")
|
|
230
|
+
if context_chars < 0:
|
|
231
|
+
raise ValueError("context_chars must be non-negative")
|
|
232
|
+
|
|
233
|
+
warnings: list[str] = []
|
|
234
|
+
conn = open_hj_index(db_path)
|
|
235
|
+
try:
|
|
236
|
+
started = time.perf_counter()
|
|
237
|
+
if len(normalized_query) >= 3:
|
|
238
|
+
rows = search_fts(conn, normalized_query, limit, book_prefix)
|
|
239
|
+
mode = "fts5-trigram"
|
|
240
|
+
elif allow_short_scan:
|
|
241
|
+
rows = search_like(conn, raw_query, limit, book_prefix)
|
|
242
|
+
mode = "like-scan-short-query"
|
|
243
|
+
warnings.append("短词查询未走 trigram 倒排索引,结果仅用于人工初筛。")
|
|
244
|
+
else:
|
|
245
|
+
rows = []
|
|
246
|
+
mode = "short-query-blocked"
|
|
247
|
+
warnings.append("少于 3 个规范化字符的查询不会命中 trigram 索引;必要时启用 allow_short_scan。")
|
|
248
|
+
elapsed_ms = (time.perf_counter() - started) * 1000
|
|
249
|
+
candidates = [
|
|
250
|
+
row_to_candidate(row, raw_query, normalized_query, context_chars)
|
|
251
|
+
for row in rows
|
|
252
|
+
]
|
|
253
|
+
finally:
|
|
254
|
+
conn.close()
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
"query": raw_query,
|
|
258
|
+
"normalized_query": normalized_query,
|
|
259
|
+
"searched_at": now_iso(),
|
|
260
|
+
"db_path": str(db_path),
|
|
261
|
+
"mode": mode,
|
|
262
|
+
"limit": limit,
|
|
263
|
+
"context_chars": context_chars,
|
|
264
|
+
"book_prefix": book_prefix,
|
|
265
|
+
"hit_count": len(candidates),
|
|
266
|
+
"elapsed_ms": elapsed_ms,
|
|
267
|
+
"warnings": warnings,
|
|
268
|
+
"evidence_policy": EVIDENCE_POLICY,
|
|
269
|
+
"results": candidates,
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def write_json_report(report: dict[str, Any], out_path: Path) -> None:
|
|
274
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
275
|
+
out_path.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def write_text_report(report: dict[str, Any], out_path: Path) -> None:
|
|
279
|
+
lines = [
|
|
280
|
+
"HJ古籍索引只读检索接口验证",
|
|
281
|
+
f"查询:{report['query']}",
|
|
282
|
+
f"规范化查询:{report['normalized_query']}",
|
|
283
|
+
f"数据库:{report['db_path']}",
|
|
284
|
+
f"模式:{report['mode']}",
|
|
285
|
+
f"命中数:{report['hit_count']}",
|
|
286
|
+
f"耗时毫秒:{report['elapsed_ms']:.3f}",
|
|
287
|
+
"",
|
|
288
|
+
"证据规则:",
|
|
289
|
+
f"- {EVIDENCE_POLICY['evidence_note']}",
|
|
290
|
+
f"- {EVIDENCE_POLICY['page_warning']}",
|
|
291
|
+
]
|
|
292
|
+
if report["warnings"]:
|
|
293
|
+
lines.extend(["", "警告:"])
|
|
294
|
+
lines.extend(f"- {warning}" for warning in report["warnings"])
|
|
295
|
+
lines.extend(["", "命中候选:"])
|
|
296
|
+
for idx, item in enumerate(report["results"], start=1):
|
|
297
|
+
lines.extend(
|
|
298
|
+
[
|
|
299
|
+
"",
|
|
300
|
+
f"{idx}. {item['title']}",
|
|
301
|
+
f" book_base/article:{item['book_base']} / {item['article_no']}",
|
|
302
|
+
f" source:{item['source']}",
|
|
303
|
+
f" hit:{item['hit_column']}@{item['position']} matched={item['matched_text'] or '(未定位)'}",
|
|
304
|
+
f" context:{item['context']}",
|
|
305
|
+
" page_basis:False;页码须另核版本。",
|
|
306
|
+
]
|
|
307
|
+
)
|
|
308
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
309
|
+
out_path.write_text("\n".join(lines), encoding="utf-8")
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def command_stats(args: argparse.Namespace) -> None:
|
|
313
|
+
print(json.dumps(inspect_hj_index(Path(args.db)), ensure_ascii=False, indent=2))
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def command_search(args: argparse.Namespace) -> None:
|
|
317
|
+
report = search_hj_index(
|
|
318
|
+
args.query,
|
|
319
|
+
db_path=Path(args.db),
|
|
320
|
+
limit=args.limit,
|
|
321
|
+
context_chars=args.context,
|
|
322
|
+
book_prefix=args.book,
|
|
323
|
+
allow_short_scan=args.allow_short_scan,
|
|
324
|
+
)
|
|
325
|
+
if args.out_json:
|
|
326
|
+
write_json_report(report, Path(args.out_json))
|
|
327
|
+
if args.out_txt:
|
|
328
|
+
write_text_report(report, Path(args.out_txt))
|
|
329
|
+
print(json.dumps(report, ensure_ascii=False, indent=2))
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
333
|
+
parser = argparse.ArgumentParser(description="中古史学术IDE HJ古籍索引只读检索接口")
|
|
334
|
+
parser.add_argument("--db", default=str(DEFAULT_DB), help="hj_4_jt_fts.sqlite path")
|
|
335
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
336
|
+
|
|
337
|
+
stats = subparsers.add_parser("stats", help="inspect index metadata using read-only access")
|
|
338
|
+
stats.set_defaults(func=command_stats)
|
|
339
|
+
|
|
340
|
+
search = subparsers.add_parser("search", help="search HJ index as source candidates")
|
|
341
|
+
search.add_argument("query")
|
|
342
|
+
search.add_argument("--limit", type=int, default=10)
|
|
343
|
+
search.add_argument("--context", type=int, default=80)
|
|
344
|
+
search.add_argument("--book", help="filter by book_base prefix, e.g. A01/A01_100")
|
|
345
|
+
search.add_argument("--allow-short-scan", action="store_true")
|
|
346
|
+
search.add_argument("--out-json", default="")
|
|
347
|
+
search.add_argument("--out-txt", default="")
|
|
348
|
+
search.set_defaults(func=command_search)
|
|
349
|
+
return parser
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def main() -> None:
|
|
353
|
+
parser = build_parser()
|
|
354
|
+
args = parser.parse_args()
|
|
355
|
+
args.func(args)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
if __name__ == "__main__":
|
|
359
|
+
main()
|
package/package.json
CHANGED
|
@@ -1,26 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobi-starweaver/ws-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "北辰计划·望舒 Polaris·WS — 面向史学与人文研究的终端智能体(WS CLI)",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"
|
|
7
|
+
"homepage": "https://gobi-starweaver.cn",
|
|
8
8
|
"bin": {
|
|
9
|
-
"ws": "cli.js"
|
|
9
|
+
"ws": "./dist/cli.js"
|
|
10
10
|
},
|
|
11
|
+
"main": "./dist/cli.js",
|
|
11
12
|
"files": [
|
|
12
|
-
"cli.js",
|
|
13
|
-
"chunks/**",
|
|
14
|
-
"templates/**",
|
|
15
|
-
"bundled/**",
|
|
16
|
-
"
|
|
17
|
-
"
|
|
13
|
+
"dist/cli.js",
|
|
14
|
+
"dist/chunks/**",
|
|
15
|
+
"dist/templates/**",
|
|
16
|
+
"dist/bundled/**",
|
|
17
|
+
"dist/mcp/**",
|
|
18
|
+
"dist/vendor/**",
|
|
19
|
+
"dist/assets/**",
|
|
18
20
|
"README.md",
|
|
19
|
-
"README-en.md",
|
|
20
21
|
"LICENSE"
|
|
21
22
|
],
|
|
22
23
|
"engines": {
|
|
23
24
|
"node": ">=22"
|
|
24
25
|
},
|
|
25
|
-
"
|
|
26
|
+
"scripts": {
|
|
27
|
+
"typecheck": "tsc -p ./ --noEmit",
|
|
28
|
+
"bundle": "node ../../scripts/esbuild.config.js",
|
|
29
|
+
"build": "npm run typecheck && npm run bundle && node ../../scripts/copy-bundle-assets.js && node -e \"import('node:fs').then(f => f.chmodSync('dist/cli.js', 0o755))\"",
|
|
30
|
+
"prepublishOnly": "npm run build",
|
|
31
|
+
"format": "prettier --write .",
|
|
32
|
+
"test": "node ../../scripts/scan-theme-drift.mjs && node src/tests/run-tests.mjs"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@gobi-starweaver/ws-core": "file:../core",
|
|
36
|
+
"chalk": "^5.6.2",
|
|
37
|
+
"ignore": "^7.0.5",
|
|
38
|
+
"ink": "^7.0.4",
|
|
39
|
+
"react": "^19.2.5",
|
|
40
|
+
"read-package-up": "^12.0.0",
|
|
41
|
+
"yargs": "^18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/yargs": "^17.0.35"
|
|
45
|
+
}
|
|
26
46
|
}
|
package/README-en.md
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
|
-
<h1>北辰计划·望舒 Polaris·WS</h1>
|
|
4
|
-
|
|
5
|
-
**A soft lamp for all who sail the sea of learning by night.**
|
|
6
|
-
|
|
7
|
-
[中文](README.md) · English
|
|
8
|
-
|
|
9
|
-
</div>
|
|
10
|
-
|
|
11
|
-
望舒 (WS) is a terminal agent from the Beichen Project (北辰计划), built for historical and humanistic research: in your command line, it inquires, reads, writes and revises with you through conversation — like a colleague sitting across your desk.
|
|
12
|
-
|
|
13
|
-
The Beichen Project is the humanities-agent series of Gobi StarWeaver (GSH), and holds that an agent for the humanities should carry both human warmth and scholarly rigor.
|
|
14
|
-
|
|
15
|
-
## Installation
|
|
16
|
-
|
|
17
|
-
One command installs everything (auto-detects your OS and architecture, installs all dependencies, and talks to exactly one domain — our official site):
|
|
18
|
-
|
|
19
|
-
**Windows** (paste in PowerShell, press Enter):
|
|
20
|
-
|
|
21
|
-
```powershell
|
|
22
|
-
irm https://gobi-starweaver.cn/dist/install.ps1 | iex
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
**macOS / Linux** (paste in a terminal, press Enter):
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
curl -fsSL https://gobi-starweaver.cn/dist/install.sh | bash
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Then **close and reopen your terminal**, and run `ws` in any project directory. On first launch, sign in with your Beichen Project account to get started (subscription verification happens automatically with login).
|
|
32
|
-
|
|
33
|
-
## What it does
|
|
34
|
-
|
|
35
|
-
- **Conversation is the work**: state your topic and materials; 望舒 searches, reads, writes and revises with you, step by step.
|
|
36
|
-
- **Historical sources at hand**: built-in Hanji corpus retrieval — dynastic histories, veritable records and collected works, with citations traceable to juan and page.
|
|
37
|
-
- **Thinking with a breath**: while waiting, you can see footprints like "consulting the Zizhi Tongjian…"; the moment the answer arrives, all is calm again.
|
|
38
|
-
- **Skills that grow**: the Skills and MCP plugin system lets 望舒 grow with your research habits.
|
|
39
|
-
- **Sign in and rest easy**: once logged in and your subscription verified, every request is relayed securely through Beichen servers — no keys ever live on your machine.
|
|
40
|
-
|
|
41
|
-
## Versioning
|
|
42
|
-
|
|
43
|
-
望舒 carries two version numbers, each governing its own domain:
|
|
44
|
-
|
|
45
|
-
- **Mind version v0.9**: the settled version of 望舒's persona, mind and writing style — how it thinks and how it writes.
|
|
46
|
-
- **Release version 0.3.0**: the semantic version of the CLI software itself — installation, upgrades and issue reports refer to this one.
|
|
47
|
-
|
|
48
|
-
## License and origin
|
|
49
|
-
|
|
50
|
-
This software is **proprietary** — copyright Gobi StarWeaver Digital History Lab (GSH), all rights reserved; the portions originating from [DeepCode](https://github.com/lessweb/deepcode-cli) remain under the MIT License as required — see [LICENSE](LICENSE). 望舒 CLI is continuously developed by GSH.
|
|
51
|
-
|
|
52
|
-
《史学智能生态系统》魏柯帆团队原创,知识产权受保护。
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|