knowflow 0.1.0__py3-none-any.whl
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.
- knowflow/__init__.py +4 -0
- knowflow/__main__.py +3 -0
- knowflow/adapters/knowledge/__init__.py +42 -0
- knowflow/adapters/knowledge/local.py +43 -0
- knowflow/adapters/knowledge/obsidian.py +64 -0
- knowflow/adapters/storage/__init__.py +62 -0
- knowflow/adapters/storage/git.py +155 -0
- knowflow/adapters/storage/local.py +110 -0
- knowflow/adapters/storage/quark.py +225 -0
- knowflow/adapters/storage/s3.py +118 -0
- knowflow/adapters/storage/webdav.py +196 -0
- knowflow/cli/__init__.py +265 -0
- knowflow/cli/template_manager.py +100 -0
- knowflow/core/__init__.py +10 -0
- knowflow/core/knowledge.py +85 -0
- knowflow/core/pipeline.py +119 -0
- knowflow/core/storage.py +167 -0
- knowflow/dashboard/__init__.py +355 -0
- knowflow/templates/daily-journal/plugin.yaml +21 -0
- knowflow/templates/football-prediction/plugin.yaml +22 -0
- knowflow/templates/project-wiki/plugin.yaml +20 -0
- knowflow/templates/research-notes/plugin.yaml +26 -0
- knowflow/templates/stock-analysis/plugin.yaml +22 -0
- knowflow/templates/web-monitor/plugin.yaml +20 -0
- knowflow/templates/worldcup-tracker/plugin.yaml +18 -0
- knowflow/transforms/__init__.py +6 -0
- knowflow/transforms/archive.py +87 -0
- knowflow/transforms/markdown_export.py +110 -0
- knowflow/transforms/profile_index.py +89 -0
- knowflow-0.1.0.dist-info/METADATA +264 -0
- knowflow-0.1.0.dist-info/RECORD +35 -0
- knowflow-0.1.0.dist-info/WHEEL +5 -0
- knowflow-0.1.0.dist-info/entry_points.txt +2 -0
- knowflow-0.1.0.dist-info/licenses/LICENSE +21 -0
- knowflow-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
knowflow/transforms/archive.py — Hot/Warm/Cold data lifecycle
|
|
3
|
+
===============================================================
|
|
4
|
+
Implements automatic data archiving based on half-life decay model.
|
|
5
|
+
"""
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import tarfile
|
|
8
|
+
import io
|
|
9
|
+
import os
|
|
10
|
+
import time
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from knowflow.core.pipeline import TransformStage, PipelineContext
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ArchiveStage(TransformStage):
|
|
16
|
+
"""Hot → Warm → Cold data archiving"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
super().__init__("archive")
|
|
20
|
+
self.hot_days = 30 # Hot retention
|
|
21
|
+
self.cold_months = 12 # Warm retention
|
|
22
|
+
|
|
23
|
+
def process(self, ctx: PipelineContext) -> dict:
|
|
24
|
+
if ctx.dry_run:
|
|
25
|
+
return self._dry_run(ctx)
|
|
26
|
+
|
|
27
|
+
warm_dir = ctx.data_dir.parent.parent / "archive" / ctx.plugin_name
|
|
28
|
+
warm_dir.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
|
|
30
|
+
now = time.time()
|
|
31
|
+
cutoff = now - (self.hot_days * 86400)
|
|
32
|
+
by_month = {}
|
|
33
|
+
freed = 0
|
|
34
|
+
|
|
35
|
+
for root, dirs, files in os.walk(str(ctx.data_dir)):
|
|
36
|
+
for fname in files:
|
|
37
|
+
fpath = Path(root) / fname
|
|
38
|
+
try:
|
|
39
|
+
mtime = fpath.stat().st_mtime
|
|
40
|
+
if mtime < cutoff:
|
|
41
|
+
rel = fpath.relative_to(ctx.data_dir.parent.parent / "data").as_posix()
|
|
42
|
+
month = datetime.fromtimestamp(mtime).strftime("%Y-%m")
|
|
43
|
+
by_month.setdefault(month, []).append((rel, fpath, mtime))
|
|
44
|
+
freed += fpath.stat().st_size
|
|
45
|
+
except OSError:
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
archives = []
|
|
49
|
+
for month, files in sorted(by_month.items()):
|
|
50
|
+
archive_path = warm_dir / f"{month}.tar.gz"
|
|
51
|
+
with tarfile.open(str(archive_path), "w:gz") as tar:
|
|
52
|
+
for rel, abspath, mtime in files:
|
|
53
|
+
content = abspath.read_bytes()
|
|
54
|
+
info = tarfile.TarInfo(name=rel)
|
|
55
|
+
info.size = len(content)
|
|
56
|
+
info.mtime = mtime
|
|
57
|
+
tar.addfile(info, io.BytesIO(content))
|
|
58
|
+
abspath.unlink()
|
|
59
|
+
archives.append(archive_path.name)
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
"status": "ok",
|
|
63
|
+
"archives": len(archives),
|
|
64
|
+
"files": sum(len(v) for v in by_month.values()),
|
|
65
|
+
"freed_kb": round(freed / 1024, 1),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
def _dry_run(self, ctx: PipelineContext) -> dict:
|
|
69
|
+
now = time.time()
|
|
70
|
+
cutoff = now - (self.hot_days * 86400)
|
|
71
|
+
candidates = []
|
|
72
|
+
|
|
73
|
+
for root, dirs, files in os.walk(str(ctx.data_dir)):
|
|
74
|
+
for fname in files:
|
|
75
|
+
fpath = Path(root) / fname
|
|
76
|
+
try:
|
|
77
|
+
if fpath.stat().st_mtime < cutoff:
|
|
78
|
+
rel = fpath.relative_to(ctx.data_dir.parent.parent / "data").as_posix()
|
|
79
|
+
candidates.append(rel)
|
|
80
|
+
except OSError:
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
"status": "ok",
|
|
85
|
+
"dry_run": True,
|
|
86
|
+
"candidates": len(candidates),
|
|
87
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""
|
|
2
|
+
knowflow/transforms/markdown_export.py — JSON → Markdown export
|
|
3
|
+
================================================================
|
|
4
|
+
Converts structured JSON data into human-readable Markdown.
|
|
5
|
+
"""
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import json
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from knowflow.core.pipeline import TransformStage, PipelineContext, PipelineError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MarkdownExport(TransformStage):
|
|
13
|
+
"""Convert JSON data files to Markdown documents"""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
super().__init__("markdown_export")
|
|
17
|
+
|
|
18
|
+
def process(self, ctx: PipelineContext) -> dict:
|
|
19
|
+
data_dir = ctx.data_dir
|
|
20
|
+
output_dir = ctx.output_dir
|
|
21
|
+
|
|
22
|
+
if not data_dir.exists():
|
|
23
|
+
return {"status": "skipped", "reason": "No data directory"}
|
|
24
|
+
|
|
25
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
generated = []
|
|
27
|
+
|
|
28
|
+
for json_file in sorted(data_dir.rglob("*.json")):
|
|
29
|
+
try:
|
|
30
|
+
data = json.loads(json_file.read_text(encoding="utf-8"))
|
|
31
|
+
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
32
|
+
ctx.plugin_config.get("_warnings", []).append(f"Skipped invalid JSON: {json_file.name}")
|
|
33
|
+
continue
|
|
34
|
+
|
|
35
|
+
md = self._to_markdown(data, json_file, ctx.plugin_name)
|
|
36
|
+
if md:
|
|
37
|
+
md_name = json_file.stem + ".md"
|
|
38
|
+
md_rel = json_file.relative_to(data_dir).parent / md_name
|
|
39
|
+
md_path = output_dir / md_rel
|
|
40
|
+
md_path.parent.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
md_path.write_text(md, encoding="utf-8")
|
|
42
|
+
generated.append(str(md_rel))
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
"status": "ok",
|
|
46
|
+
"generated": len(generated),
|
|
47
|
+
"files": generated[:5],
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
def _to_markdown(self, data: dict, json_path: Path, plugin: str) -> str:
|
|
51
|
+
if not data:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
lines = []
|
|
55
|
+
lines.append(f"# {plugin.title()} — {json_path.stem}")
|
|
56
|
+
lines.append("")
|
|
57
|
+
lines.append(f"> 自动生成 {datetime.now().strftime('%Y-%m-%d %H:%M')}")
|
|
58
|
+
lines.append(f"> 源: `{json_path.relative_to(json_path.parent.parent.parent)}`")
|
|
59
|
+
lines.append("")
|
|
60
|
+
lines.append("---")
|
|
61
|
+
lines.append("")
|
|
62
|
+
|
|
63
|
+
self._render(lines, data, 2)
|
|
64
|
+
|
|
65
|
+
return "\n".join(lines)
|
|
66
|
+
|
|
67
|
+
def _render(self, lines: list, data, level: int):
|
|
68
|
+
if isinstance(data, dict):
|
|
69
|
+
for k, v in data.items():
|
|
70
|
+
if isinstance(v, (dict, list)) and not isinstance(v, str):
|
|
71
|
+
h = k.replace("_", " ").title()
|
|
72
|
+
lines.append(f"{'#' * level} {h}")
|
|
73
|
+
lines.append("")
|
|
74
|
+
self._render(lines, v, min(level + 1, 6))
|
|
75
|
+
elif isinstance(v, list) and v and isinstance(v[0], dict):
|
|
76
|
+
h = k.replace("_", " ").title()
|
|
77
|
+
lines.append(f"{'#' * level} {h}")
|
|
78
|
+
lines.append("")
|
|
79
|
+
self._render_table(lines, v)
|
|
80
|
+
else:
|
|
81
|
+
label = k.replace("_", " ").title()
|
|
82
|
+
lines.append(f"- **{label}**: {self._fmt(v)}")
|
|
83
|
+
lines.append("")
|
|
84
|
+
elif isinstance(data, list):
|
|
85
|
+
if data and isinstance(data[0], dict):
|
|
86
|
+
self._render_table(lines, data)
|
|
87
|
+
else:
|
|
88
|
+
for item in data:
|
|
89
|
+
lines.append(f"- {self._fmt(item)}")
|
|
90
|
+
lines.append("")
|
|
91
|
+
|
|
92
|
+
def _render_table(self, lines: list, items: list[dict]):
|
|
93
|
+
if not items:
|
|
94
|
+
return
|
|
95
|
+
keys = list(dict.fromkeys(k for item in items for k in item.keys()
|
|
96
|
+
if not isinstance(item.get(k), (dict, list))))
|
|
97
|
+
if not keys:
|
|
98
|
+
return
|
|
99
|
+
lines.append("| " + " | ".join(k.replace("_", " ").title() for k in keys) + " |")
|
|
100
|
+
lines.append("| " + " | ".join("---" for _ in keys) + " |")
|
|
101
|
+
for item in items:
|
|
102
|
+
row = "| " + " | ".join(str(self._fmt(item.get(k, "")))[:60] for k in keys) + " |"
|
|
103
|
+
lines.append(row)
|
|
104
|
+
lines.append("")
|
|
105
|
+
|
|
106
|
+
def _fmt(self, v) -> str:
|
|
107
|
+
if v is None: return "—"
|
|
108
|
+
if isinstance(v, bool): return "✅" if v else "❌"
|
|
109
|
+
if isinstance(v, float): return f"{v:.2f}"
|
|
110
|
+
return str(v)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
knowflow/transforms/profile_index.py — Auto-generate PROFILE.md
|
|
3
|
+
===============================================================
|
|
4
|
+
Creates a human-readable index of all data in the knowledge base.
|
|
5
|
+
"""
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
import os
|
|
9
|
+
from knowflow.core.pipeline import TransformStage, PipelineContext
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ProfileIndex(TransformStage):
|
|
13
|
+
"""Generate PROFILE.md index of the knowledge base"""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
super().__init__("profile_index")
|
|
17
|
+
|
|
18
|
+
def process(self, ctx: PipelineContext) -> dict:
|
|
19
|
+
root = ctx.data_dir.parent.parent # ~/knowflow-data or ~/knowledge
|
|
20
|
+
profile_path = root / "PROFILE.md"
|
|
21
|
+
manifest_path = root / "index" / "manifest.json"
|
|
22
|
+
|
|
23
|
+
plugins = {}
|
|
24
|
+
data_dir = ctx.data_dir.parent # data/
|
|
25
|
+
if data_dir.exists():
|
|
26
|
+
for plugin_dir in sorted(data_dir.iterdir()):
|
|
27
|
+
if plugin_dir.is_dir():
|
|
28
|
+
plugins[plugin_dir.name] = self._scan_dir(plugin_dir)
|
|
29
|
+
|
|
30
|
+
profile = self._build_profile(plugins, ctx.plugin_config)
|
|
31
|
+
profile_path.write_text(profile, encoding="utf-8")
|
|
32
|
+
|
|
33
|
+
import json
|
|
34
|
+
manifest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
manifest_path.write_text(
|
|
36
|
+
json.dumps({
|
|
37
|
+
"generated_at": datetime.now().isoformat(),
|
|
38
|
+
"plugins": list(plugins.keys()),
|
|
39
|
+
}, ensure_ascii=False, indent=2),
|
|
40
|
+
encoding="utf-8"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
"status": "ok",
|
|
45
|
+
"profile_size": len(profile),
|
|
46
|
+
"plugins": list(plugins.keys()),
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
def _scan_dir(self, plugin_dir: Path) -> dict:
|
|
50
|
+
count = 0
|
|
51
|
+
size = 0
|
|
52
|
+
latest = 0.0
|
|
53
|
+
for root, dirs, files in os.walk(str(plugin_dir)):
|
|
54
|
+
for f in files:
|
|
55
|
+
try:
|
|
56
|
+
st = (Path(root) / f).stat()
|
|
57
|
+
count += 1
|
|
58
|
+
size += st.st_size
|
|
59
|
+
latest = max(latest, st.st_mtime)
|
|
60
|
+
except OSError:
|
|
61
|
+
continue
|
|
62
|
+
return {
|
|
63
|
+
"files": count,
|
|
64
|
+
"size_kb": round(size / 1024, 1),
|
|
65
|
+
"updated": datetime.fromtimestamp(latest).isoformat() if latest else "—",
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
def _build_profile(self, plugins: dict, config: dict) -> str:
|
|
69
|
+
lines = ["# 🧠 KnowFlow Knowledge Base", "",
|
|
70
|
+
f"自动生成 {datetime.now().strftime('%Y-%m-%d %H:%M')}", "",
|
|
71
|
+
"## 📊 概览", "",
|
|
72
|
+
f"| 指标 | 数值 |",
|
|
73
|
+
f"|------|------|",
|
|
74
|
+
f"| 插件数 | {len(plugins)} |",
|
|
75
|
+
f"| 总文件 | {sum(p['files'] for p in plugins.values())} |",
|
|
76
|
+
f"| 总大小 | {sum(p['size_kb'] for p in plugins.values()):.1f} KB |",
|
|
77
|
+
"", "## 🔌 插件", ""]
|
|
78
|
+
|
|
79
|
+
for name, st in plugins.items():
|
|
80
|
+
icon = {"football": "⚽", "stock": "📈", "worldcup": "🏆"}
|
|
81
|
+
lines.append(f"### {icon.get(name, '📦')} {name.title()}")
|
|
82
|
+
lines.append(f"- 文件: {st['files']}")
|
|
83
|
+
lines.append(f"- 大小: {st['size_kb']} KB")
|
|
84
|
+
lines.append(f"- 更新: {st['updated']}")
|
|
85
|
+
lines.append("")
|
|
86
|
+
|
|
87
|
+
lines.append("---")
|
|
88
|
+
lines.append("> KnowFlow — Agent Knowledge Pipeline")
|
|
89
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: knowflow
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agent Knowledge Pipeline — storage-agnostic, KB-agnostic, plug-and-play pipeline that turns raw agent JSON into versioned, human-readable docs
|
|
5
|
+
Author: qypvip
|
|
6
|
+
Author-email: qypvip <yanpeng898@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/qypvip/knowflow
|
|
9
|
+
Project-URL: Repository, https://github.com/qypvip/knowflow
|
|
10
|
+
Project-URL: Issues, https://github.com/qypvip/knowflow/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/qypvip/knowflow/commits/main
|
|
12
|
+
Keywords: agent,ai-agent,knowledge-base,pipeline,llm,markdown,obsidian,automation,data-pipeline,etl
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.8
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: pyyaml>=5.1
|
|
30
|
+
Provides-Extra: s3
|
|
31
|
+
Requires-Dist: boto3; extra == "s3"
|
|
32
|
+
Provides-Extra: notion
|
|
33
|
+
Requires-Dist: notion-client; extra == "notion"
|
|
34
|
+
Provides-Extra: ima
|
|
35
|
+
Requires-Dist: requests; extra == "ima"
|
|
36
|
+
Provides-Extra: webdav
|
|
37
|
+
Requires-Dist: requests; extra == "webdav"
|
|
38
|
+
Provides-Extra: all
|
|
39
|
+
Requires-Dist: boto3; extra == "all"
|
|
40
|
+
Requires-Dist: notion-client; extra == "all"
|
|
41
|
+
Requires-Dist: requests; extra == "all"
|
|
42
|
+
Dynamic: author
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
Dynamic: requires-python
|
|
45
|
+
|
|
46
|
+
# KnowFlow 🧠
|
|
47
|
+
|
|
48
|
+
**Agent Knowledge Pipeline** — 存储无关、知识库无关、即装即用。
|
|
49
|
+
|
|
50
|
+
让 AI Agent 产出的数据自动走完:**写入 → 版本控制 → 多端同步 → 人类可读** 的全流程。
|
|
51
|
+
|
|
52
|
+

|
|
53
|
+
[](https://pypi.org/project/knowflow/)
|
|
54
|
+
[](https://pypi.org/project/knowflow/)
|
|
55
|
+
[](LICENSE)
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install knowflow
|
|
59
|
+
|
|
60
|
+
# 从模板创建新项目
|
|
61
|
+
knowflow init football-prediction --dir my-football-bot
|
|
62
|
+
cd my-football-bot
|
|
63
|
+
|
|
64
|
+
# Agent写入数据 → 自动转 Markdown 文档
|
|
65
|
+
echo '{"match": "巴西vs阿根廷", "prediction": "巴西胜"}' > football/data/prediction.json
|
|
66
|
+
knowflow run
|
|
67
|
+
|
|
68
|
+
# 查看报告
|
|
69
|
+
cat output/football/prediction.md
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 🤔 这解决什么问题
|
|
73
|
+
|
|
74
|
+
如果你用 AI Agent 干活,大概率遇到过这三件事:
|
|
75
|
+
|
|
76
|
+
1. **数据散落** —— Agent 每跑一次就吐几个 JSON,几十次之后你根本找不到“上周三那次分析结果在哪”。
|
|
77
|
+
2. **机器看得懂,人看不懂** —— JSON 一行行挤在一起,想回顾得自己拼。
|
|
78
|
+
3. **越用越乱** —— 数据只增不减,几个月后目录里几千个文件,不敢删也不知道删哪个。
|
|
79
|
+
|
|
80
|
+
KnowFlow 干的就是这三件:**Agent 只管往固定目录写 JSON,剩下的(转成人类可读的 Markdown、
|
|
81
|
+
自动分热/温/冷三层归档、生成索引、Git 记录每次变更)全交给它。**
|
|
82
|
+
|
|
83
|
+
一句话:**Agent 负责产出,KnowFlow 负责让它留下来还有用。**
|
|
84
|
+
|
|
85
|
+
## ✨ 特性
|
|
86
|
+
|
|
87
|
+
| 特性 | 说明 | 状态 |
|
|
88
|
+
|------|------|------|
|
|
89
|
+
| 🔌 **存储无关** | Git / 本地 / S3 / 夸克网盘 / WebDAV | ✅ 已实现 |
|
|
90
|
+
| 📚 **知识库无关** | 本地目录 / Obsidian | ✅ 已实现 |
|
|
91
|
+
| 📦 **模板系统** | 足球预测、股票分析、日记、Wiki 等 **7 个** | ✅ 已实现 |
|
|
92
|
+
| 🗄️ **自动归档** | 热→温→冷三层,数据不会无限膨胀 | ✅ 已实现 |
|
|
93
|
+
| 🔄 **版本控制** | Git 后端自动记录每次变更 | ✅ 已实现 |
|
|
94
|
+
| 🧪 **测试** | 68 个单元测试 + GitHub Actions | ✅ 已实现 |
|
|
95
|
+
| 🔓 **开源** | MIT 协议,可商用可二次开发 | ✅ |
|
|
96
|
+
|
|
97
|
+
> 仅依赖 `pyyaml`(非"零依赖");Notion / IMA / MkDocs 后端**尚未实现**,欢迎 PR。
|
|
98
|
+
|
|
99
|
+
## 🚀 安装
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pip install knowflow
|
|
103
|
+
|
|
104
|
+
# 可选后端
|
|
105
|
+
pip install "knowflow[s3]" # S3 / MinIO / R2
|
|
106
|
+
pip install "knowflow[all]" # 全部后端
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
从源码安装(开发用):
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
git clone https://github.com/qypvip/knowflow.git
|
|
113
|
+
cd knowflow
|
|
114
|
+
pip install -e .
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
国内网络拉不动 GitHub 时,可用镜像前缀:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
git clone https://ghproxy.net/https://github.com/qypvip/knowflow.git
|
|
121
|
+
# 或从 Gitee 克隆
|
|
122
|
+
git clone git@gitee.com:qypvip/knowflow.git
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## ⚡ 快速开始
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# 查看可用模板
|
|
129
|
+
knowflow list
|
|
130
|
+
|
|
131
|
+
# 初始化项目
|
|
132
|
+
knowflow init daily-journal --dir my-diary
|
|
133
|
+
cd my-diary
|
|
134
|
+
|
|
135
|
+
# 写入一些数据
|
|
136
|
+
mkdir -p diary/data/entries
|
|
137
|
+
cat > diary/data/entries/2026-10-02.json << 'EOF'
|
|
138
|
+
{
|
|
139
|
+
"date": "2026-10-02",
|
|
140
|
+
"title": "今天学习了 KnowFlow",
|
|
141
|
+
"mood": "开心",
|
|
142
|
+
"notes": "终于有了一个统一的知识管理工具"
|
|
143
|
+
}
|
|
144
|
+
EOF
|
|
145
|
+
|
|
146
|
+
# 运行管道(JSON → Markdown → 索引 → 归档)
|
|
147
|
+
knowflow run
|
|
148
|
+
|
|
149
|
+
# 查看输出
|
|
150
|
+
cat output/diary/2026-10-02.md
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## 🧪 开发与测试
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
pip install -e . pytest
|
|
157
|
+
pytest -q # 68 passed
|
|
158
|
+
python -m knowflow list
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
CI 在 Python 3.9 / 3.11 / 3.12 上跑全量测试,另有一个"零额外依赖"冒烟任务
|
|
162
|
+
验证核心模块在只装 `pyyaml` 时可 import、CLI 可用。
|
|
163
|
+
|
|
164
|
+
## 📦 模板
|
|
165
|
+
|
|
166
|
+
| 模板 | 命令 | 适用场景 |
|
|
167
|
+
|------|------|---------|
|
|
168
|
+
| ⚽ football-prediction | `knowflow init football-prediction` | 比赛预测+复盘闭环 |
|
|
169
|
+
| 📈 stock-analysis | `knowflow init stock-analysis` | 多策略股票分析 |
|
|
170
|
+
| 🏆 worldcup-tracker | `knowflow init worldcup-tracker` | 赛事名单追踪 |
|
|
171
|
+
| 📝 daily-journal | `knowflow init daily-journal` | 个人日记+归档 |
|
|
172
|
+
| 📚 project-wiki | `knowflow init project-wiki` | 项目文档自动化 |
|
|
173
|
+
| 🌐 web-monitor | `knowflow init web-monitor` | 网页内容监控 |
|
|
174
|
+
| 🔬 research-notes | `knowflow init research-notes` | 学术笔记管理 |
|
|
175
|
+
|
|
176
|
+
## 🔌 存储后端
|
|
177
|
+
|
|
178
|
+
| 后端 | 类 | 额外依赖 | 适用场景 |
|
|
179
|
+
|------|----|---------|---------|
|
|
180
|
+
| 🔄 `git`(默认) | `GitStorage` | git CLI | 本地 + 版本历史 |
|
|
181
|
+
| 📁 `local` | `LocalStorage` | 无 | 最简,无版本 |
|
|
182
|
+
| ☁️ `s3` | `S3Storage` | boto3 | AWS / MinIO / R2 |
|
|
183
|
+
| 🅰️ `quark` | `QuarkStorage` | kuake_cli | 夸克网盘 |
|
|
184
|
+
| 🌐 `webdav` | `WebDAVStorage` | requests | NextCloud / NAS |
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
knowflow init football-prediction --storage git
|
|
188
|
+
knowflow init football-prediction --storage local
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## 📚 知识库后端
|
|
192
|
+
|
|
193
|
+
| 后端 | 类 |
|
|
194
|
+
|------|----|
|
|
195
|
+
| 📁 `local`(默认) | `LocalKB` |
|
|
196
|
+
| 📝 `obsidian` | `ObsidianKB` |
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
knowflow init football-prediction --kb obsidian
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Python 侧用法:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
from knowflow.adapters.storage import create_storage
|
|
206
|
+
from knowflow.adapters.knowledge import create_knowledge # create_kb 为别名
|
|
207
|
+
|
|
208
|
+
store = create_storage("git", path="~/my-data") # 扁平参数
|
|
209
|
+
store = create_storage("git", config={"path": "~/my-data"}) # 或显式 config
|
|
210
|
+
|
|
211
|
+
kb = create_knowledge("local", path="~/my-output")
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## 🧪 命令行
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
knowflow list # 列出所有模板
|
|
218
|
+
knowflow init <name> # 从模板创建项目
|
|
219
|
+
knowflow run # 运行数据管道
|
|
220
|
+
knowflow status # 查看仓库状态
|
|
221
|
+
knowflow commit "msg" # 手动提交
|
|
222
|
+
knowflow archive # 手动归档旧数据
|
|
223
|
+
knowflow version # 查看版本
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## 🏗️ 架构
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
Agent写入数据
|
|
230
|
+
↓
|
|
231
|
+
StorageAdapter ← 可插拔(Git/本地/S3/夸克/WebDAV)
|
|
232
|
+
↓
|
|
233
|
+
Pipeline (Markdown导出→归档→索引)
|
|
234
|
+
↓
|
|
235
|
+
KnowledgeAdapter ← 可插拔(本地/Obsidian)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### 加一个存储后端(两步)
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
from knowflow.core.storage import StorageAdapter
|
|
242
|
+
|
|
243
|
+
class MyStorage(StorageAdapter):
|
|
244
|
+
def read(self, path): ...
|
|
245
|
+
def read_binary(self, path): ...
|
|
246
|
+
def write(self, path, content): ...
|
|
247
|
+
def write_binary(self, path, content): ...
|
|
248
|
+
def delete(self, path): ...
|
|
249
|
+
def exists(self, path): ...
|
|
250
|
+
def list(self, prefix=""): ...
|
|
251
|
+
def walk(self, prefix=""): ...
|
|
252
|
+
|
|
253
|
+
from knowflow.adapters.storage import register
|
|
254
|
+
register("mystorage", MyStorage) # 完成
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## 📄 协议
|
|
258
|
+
|
|
259
|
+
MIT
|
|
260
|
+
|
|
261
|
+
## 🔗 镜像
|
|
262
|
+
|
|
263
|
+
- GitHub: <https://github.com/qypvip/knowflow>
|
|
264
|
+
- Gitee: <https://gitee.com/qypvip/knowflow>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
knowflow/__init__.py,sha256=k9Io_mfdL-fSR79D6TNViRHVR3fiLNqqo2BAx-rpNjg,110
|
|
2
|
+
knowflow/__main__.py,sha256=WC0Qg2bFvqPawzfhuoxP68tVEpZJT1Aacu_CFXVGl4w,103
|
|
3
|
+
knowflow/adapters/knowledge/__init__.py,sha256=iEQ-Hr1iJvMBLblp8lVSv7A0qScvsAasN3Qq_QFFVdo,1353
|
|
4
|
+
knowflow/adapters/knowledge/local.py,sha256=e4ZjtV_pirYnIRIgOtaw9aUF8oN7IwUAGAtRRS5oYgE,1648
|
|
5
|
+
knowflow/adapters/knowledge/obsidian.py,sha256=ZeDutTVeW94hRN_owbyiUTi40qEzwReVUNT9ji1Wmqs,2549
|
|
6
|
+
knowflow/adapters/storage/__init__.py,sha256=bb6J19_LrmLSp_nCdeN28r-xoopp3QR89yvjBpQXgvw,1874
|
|
7
|
+
knowflow/adapters/storage/git.py,sha256=6iPwtWCY2GDQg0qomOKjOCAgdW-X2PStypUKTmKJGpg,5636
|
|
8
|
+
knowflow/adapters/storage/local.py,sha256=fu3EmeydRLTtoL1HYDk2OL_pPqZiRMYdocbLUCpToVw,3693
|
|
9
|
+
knowflow/adapters/storage/quark.py,sha256=CgyfSjkIcCUa_cfmSX2JRZ6hi2MFKp95CGUS99MofhQ,7755
|
|
10
|
+
knowflow/adapters/storage/s3.py,sha256=NjSsl-ih4jl6XTxcM6JTMAHh18mClB2BWl7yH6X1vu4,4174
|
|
11
|
+
knowflow/adapters/storage/webdav.py,sha256=2t9Nsbce3URfRGI_mAYEju2KynuuRun5KmW_WTotk5g,7182
|
|
12
|
+
knowflow/cli/__init__.py,sha256=8X1bDnZKnPNZ3xJezWwAPS2fDkbJe4VUoCFu5x3RMM0,8411
|
|
13
|
+
knowflow/cli/template_manager.py,sha256=ud57YtwC-USo9Lkv3AvX7QDH0I6uUeAwphyKHJDm7ho,3122
|
|
14
|
+
knowflow/core/__init__.py,sha256=-v8VwB_BQgRY9vigAip-WX38oIljmbzREvp6VuzAuVQ,500
|
|
15
|
+
knowflow/core/knowledge.py,sha256=tXR8G6gsJgLxI9yKZ_mJSE6TrZsVoQjAcl4RWUSzZ8k,2456
|
|
16
|
+
knowflow/core/pipeline.py,sha256=uu3y5sd9t1bYwVdRxyecIQNy2l0YZXounpEZMNTiAfM,3929
|
|
17
|
+
knowflow/core/storage.py,sha256=_7HE4BMBsjPK52APIv2jF9jIzNHFSt7lSxd4cV9L_vM,5868
|
|
18
|
+
knowflow/dashboard/__init__.py,sha256=PHFtuFIqeDPpPW8T23CfoFiHzNE_XCWFSI6bRMjU2AU,14831
|
|
19
|
+
knowflow/templates/daily-journal/plugin.yaml,sha256=6zCXD2eC9rUccMEn99gCbvOct2BmzsSxVc7lTZD2wFI,414
|
|
20
|
+
knowflow/templates/football-prediction/plugin.yaml,sha256=YLafAZuBww1q_4EAcV4PDSIeZD_plcDfjrFDzcZxsyA,514
|
|
21
|
+
knowflow/templates/project-wiki/plugin.yaml,sha256=AxgXMbhkrFhcs6sJFkCs7bPUgwhsgcWDVMNyD4-rKJE,324
|
|
22
|
+
knowflow/templates/research-notes/plugin.yaml,sha256=YylaNcnMX_75CJWfdjz4rxpsrxiM_oh69qJSRgGUgR4,461
|
|
23
|
+
knowflow/templates/stock-analysis/plugin.yaml,sha256=QQrwZBuLhm2xKhinJGl5jTkqWtBHBuBS4Agg3VTE6xY,495
|
|
24
|
+
knowflow/templates/web-monitor/plugin.yaml,sha256=aYxbmX8ZILprBl8t1Q4OMMtf-Dpq0PBB1EP0SJgO2xk,380
|
|
25
|
+
knowflow/templates/worldcup-tracker/plugin.yaml,sha256=GS8uVmZXd3ZyRrDg8LaECIcfJGu1kXwBA-5HWm1hxtc,375
|
|
26
|
+
knowflow/transforms/__init__.py,sha256=YRwAmekjguNAhDE49k7rvhA_MEEBJzycVfcSdfQR9AU,218
|
|
27
|
+
knowflow/transforms/archive.py,sha256=VHKi_SdClSQsAu64vnHY6pWlhzj6gb16IqCb7vlvK8A,3146
|
|
28
|
+
knowflow/transforms/markdown_export.py,sha256=HpKUGQUiyokkxYxllKgQ_R9nv9czcBtuSblTmFarsdo,4337
|
|
29
|
+
knowflow/transforms/profile_index.py,sha256=0YWOMYkyx3DssbVIj1PwZyh0TjSacD1bL0gbddXu-bA,3409
|
|
30
|
+
knowflow-0.1.0.dist-info/licenses/LICENSE,sha256=2zy51WOPBWAZZDgcq84A7tuTWNxoOkDsNDaUMw8R0IU,1078
|
|
31
|
+
knowflow-0.1.0.dist-info/METADATA,sha256=ZCw-PlZpit-l56vnAZELeVMaQjl0tVdbb4akEQHOPiY,8517
|
|
32
|
+
knowflow-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
33
|
+
knowflow-0.1.0.dist-info/entry_points.txt,sha256=W6ZiPq6XlqfFLq6KX5ExpDMHwxHZUdi8ds0w38977fw,47
|
|
34
|
+
knowflow-0.1.0.dist-info/top_level.txt,sha256=8R0Q-41X-rzabioEICJyyX4cUAukG98VI-30ATM4Yfw,9
|
|
35
|
+
knowflow-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 KnowFlow Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
knowflow
|