agent-sin 0.1.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/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/assets/logo.png +0 -0
- package/builtin-skills/_shared/_models_lib.py +227 -0
- package/builtin-skills/_shared/_profile_lib.py +98 -0
- package/builtin-skills/_shared/_schedules_lib.py +313 -0
- package/builtin-skills/_shared/_skill_settings_lib.py +153 -0
- package/builtin-skills/_shared/i18n.py +26 -0
- package/builtin-skills/memo-delete/main.py +155 -0
- package/builtin-skills/memo-delete/skill.yaml +57 -0
- package/builtin-skills/memo-index/main.py +178 -0
- package/builtin-skills/memo-index/skill.yaml +53 -0
- package/builtin-skills/memo-save/README.md +5 -0
- package/builtin-skills/memo-save/main.py +74 -0
- package/builtin-skills/memo-save/skill.yaml +52 -0
- package/builtin-skills/memo-search/README.md +10 -0
- package/builtin-skills/memo-search/main.py +97 -0
- package/builtin-skills/memo-search/skill.yaml +51 -0
- package/builtin-skills/memo-vector-search/main.py +121 -0
- package/builtin-skills/memo-vector-search/skill.yaml +53 -0
- package/builtin-skills/model-add/main.py +180 -0
- package/builtin-skills/model-add/skill.yaml +112 -0
- package/builtin-skills/model-list/main.py +93 -0
- package/builtin-skills/model-list/skill.yaml +48 -0
- package/builtin-skills/model-set/main.py +123 -0
- package/builtin-skills/model-set/skill.yaml +69 -0
- package/builtin-skills/profile-delete/_profile_lib.py +98 -0
- package/builtin-skills/profile-delete/main.py +98 -0
- package/builtin-skills/profile-delete/skill.yaml +64 -0
- package/builtin-skills/profile-edit/_profile_lib.py +98 -0
- package/builtin-skills/profile-edit/main.py +97 -0
- package/builtin-skills/profile-edit/skill.yaml +72 -0
- package/builtin-skills/profile-save/main.py +52 -0
- package/builtin-skills/profile-save/skill.yaml +69 -0
- package/builtin-skills/schedule-add/_schedules_lib.py +303 -0
- package/builtin-skills/schedule-add/main.py +137 -0
- package/builtin-skills/schedule-add/skill.yaml +94 -0
- package/builtin-skills/schedule-list/_schedules_lib.py +303 -0
- package/builtin-skills/schedule-list/main.py +86 -0
- package/builtin-skills/schedule-list/skill.yaml +45 -0
- package/builtin-skills/schedule-remove/_schedules_lib.py +303 -0
- package/builtin-skills/schedule-remove/main.py +69 -0
- package/builtin-skills/schedule-remove/skill.yaml +49 -0
- package/builtin-skills/schedule-toggle/_schedules_lib.py +303 -0
- package/builtin-skills/schedule-toggle/main.py +78 -0
- package/builtin-skills/schedule-toggle/skill.yaml +61 -0
- package/builtin-skills/skills-disable/main.py +63 -0
- package/builtin-skills/skills-disable/skill.yaml +52 -0
- package/builtin-skills/skills-enable/main.py +62 -0
- package/builtin-skills/skills-enable/skill.yaml +51 -0
- package/builtin-skills/todo-add/main.py +68 -0
- package/builtin-skills/todo-add/skill.yaml +53 -0
- package/builtin-skills/todo-delete/main.py +65 -0
- package/builtin-skills/todo-delete/skill.yaml +47 -0
- package/builtin-skills/todo-done/main.py +75 -0
- package/builtin-skills/todo-done/skill.yaml +47 -0
- package/builtin-skills/todo-list/main.py +91 -0
- package/builtin-skills/todo-list/skill.yaml +48 -0
- package/builtin-skills/todo-tick/main.py +125 -0
- package/builtin-skills/todo-tick/skill.yaml +48 -0
- package/dist/builder/build-action-classifier.d.ts +18 -0
- package/dist/builder/build-action-classifier.js +142 -0
- package/dist/builder/build-commands.d.ts +19 -0
- package/dist/builder/build-commands.js +133 -0
- package/dist/builder/build-flow.d.ts +72 -0
- package/dist/builder/build-flow.js +416 -0
- package/dist/builder/builder-session.d.ts +117 -0
- package/dist/builder/builder-session.js +1129 -0
- package/dist/builder/conversation-router.d.ts +22 -0
- package/dist/builder/conversation-router.js +69 -0
- package/dist/builder/intent-runtime-store.d.ts +7 -0
- package/dist/builder/intent-runtime-store.js +60 -0
- package/dist/builder/progress-format.d.ts +7 -0
- package/dist/builder/progress-format.js +46 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +2835 -0
- package/dist/cli/spinner.d.ts +30 -0
- package/dist/cli/spinner.js +164 -0
- package/dist/core/ai-provider.d.ts +75 -0
- package/dist/core/ai-provider.js +678 -0
- package/dist/core/builtin-skills.d.ts +27 -0
- package/dist/core/builtin-skills.js +120 -0
- package/dist/core/chat-engine.d.ts +70 -0
- package/dist/core/chat-engine.js +812 -0
- package/dist/core/config.d.ts +127 -0
- package/dist/core/config.js +1379 -0
- package/dist/core/daily-memory-promotion.d.ts +21 -0
- package/dist/core/daily-memory-promotion.js +422 -0
- package/dist/core/i18n.d.ts +23 -0
- package/dist/core/i18n.js +167 -0
- package/dist/core/info-lines.d.ts +5 -0
- package/dist/core/info-lines.js +39 -0
- package/dist/core/input-schema.d.ts +2 -0
- package/dist/core/input-schema.js +156 -0
- package/dist/core/intent-router.d.ts +27 -0
- package/dist/core/intent-router.js +160 -0
- package/dist/core/logger.d.ts +60 -0
- package/dist/core/logger.js +240 -0
- package/dist/core/memory.d.ts +10 -0
- package/dist/core/memory.js +72 -0
- package/dist/core/message-utils.d.ts +13 -0
- package/dist/core/message-utils.js +104 -0
- package/dist/core/notifier.d.ts +17 -0
- package/dist/core/notifier.js +424 -0
- package/dist/core/output-writer.d.ts +13 -0
- package/dist/core/output-writer.js +100 -0
- package/dist/core/plan-decision.d.ts +16 -0
- package/dist/core/plan-decision.js +88 -0
- package/dist/core/profile-memory.d.ts +17 -0
- package/dist/core/profile-memory.js +142 -0
- package/dist/core/runtime.d.ts +50 -0
- package/dist/core/runtime.js +187 -0
- package/dist/core/scheduler.d.ts +28 -0
- package/dist/core/scheduler.js +155 -0
- package/dist/core/secrets.d.ts +31 -0
- package/dist/core/secrets.js +214 -0
- package/dist/core/service.d.ts +35 -0
- package/dist/core/service.js +479 -0
- package/dist/core/skill-planner.d.ts +24 -0
- package/dist/core/skill-planner.js +100 -0
- package/dist/core/skill-registry.d.ts +98 -0
- package/dist/core/skill-registry.js +319 -0
- package/dist/core/skill-scaffold.d.ts +33 -0
- package/dist/core/skill-scaffold.js +256 -0
- package/dist/core/skill-settings.d.ts +11 -0
- package/dist/core/skill-settings.js +63 -0
- package/dist/core/transfer.d.ts +31 -0
- package/dist/core/transfer.js +270 -0
- package/dist/core/update-notifier.d.ts +2 -0
- package/dist/core/update-notifier.js +140 -0
- package/dist/discord/bot.d.ts +96 -0
- package/dist/discord/bot.js +2424 -0
- package/dist/runtimes/codex-app-server.d.ts +53 -0
- package/dist/runtimes/codex-app-server.js +305 -0
- package/dist/runtimes/python-runner.d.ts +7 -0
- package/dist/runtimes/python-runner.js +302 -0
- package/dist/runtimes/typescript-runner.d.ts +5 -0
- package/dist/runtimes/typescript-runner.js +172 -0
- package/dist/skills-sdk/types.d.ts +38 -0
- package/dist/skills-sdk/types.js +1 -0
- package/dist/telegram/bot.d.ts +94 -0
- package/dist/telegram/bot.js +1219 -0
- package/install.ps1 +132 -0
- package/install.sh +130 -0
- package/package.json +60 -0
- package/templates/skill-python/main.py +74 -0
- package/templates/skill-python/skill.yaml +48 -0
- package/templates/skill-typescript/main.ts +87 -0
- package/templates/skill-typescript/skill.yaml +42 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Builtin: memo-index
|
|
2
|
+
|
|
3
|
+
notes_dir 配下の Markdown を読んで、各 "- {timestamp} {text}" 行を
|
|
4
|
+
Chroma の collection (default: memo) に upsert する。
|
|
5
|
+
|
|
6
|
+
入力:
|
|
7
|
+
args.since: ISO8601 文字列 (この時刻より古いメモはスキップ、任意)
|
|
8
|
+
args.collection: 索引コレクション名 (default: memo)
|
|
9
|
+
|
|
10
|
+
出力:
|
|
11
|
+
data: {added, skipped, total, collection}
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import hashlib
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_shared"))
|
|
23
|
+
from i18n import localizer # noqa: E402
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
EMBED_MODEL = "paraphrase-multilingual-MiniLM-L12-v2"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def run(ctx, input):
|
|
30
|
+
loc = localizer(input)
|
|
31
|
+
args = input.get("args", {})
|
|
32
|
+
since_str = args.get("since")
|
|
33
|
+
collection_name = args.get("collection", "memo")
|
|
34
|
+
sources = input.get("sources", {})
|
|
35
|
+
notes_dir = sources.get("notes_dir")
|
|
36
|
+
index_dir = sources.get("index_dir")
|
|
37
|
+
|
|
38
|
+
if not notes_dir or not index_dir:
|
|
39
|
+
return result_error(
|
|
40
|
+
loc.t("Cannot index", "索引化できません"),
|
|
41
|
+
loc.t("notes_dir or index_dir was not provided by the runtime.", "notes_dir または index_dir が Runtime から渡されていません"),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
import chromadb
|
|
46
|
+
from chromadb.utils.embedding_functions import (
|
|
47
|
+
SentenceTransformerEmbeddingFunction,
|
|
48
|
+
)
|
|
49
|
+
except ImportError as e:
|
|
50
|
+
ctx.log.error(f"memo-index: missing dependency: {e}")
|
|
51
|
+
return result_error(
|
|
52
|
+
loc.t("chromadb not found", "chromadb が見つかりません"),
|
|
53
|
+
loc.t(
|
|
54
|
+
"Run: python3 -m venv ~/.agent-sin/.venv && ~/.agent-sin/.venv/bin/pip install chromadb sentence-transformers",
|
|
55
|
+
"次を実行: python3 -m venv ~/.agent-sin/.venv && ~/.agent-sin/.venv/bin/pip install chromadb sentence-transformers",
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
chroma_path = Path(index_dir).expanduser().resolve() / "local-index" / "chroma"
|
|
60
|
+
chroma_path.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
|
|
62
|
+
notes_root = Path(notes_dir).expanduser().resolve()
|
|
63
|
+
if not notes_root.exists():
|
|
64
|
+
ctx.log.info("memo-index: notes_dir does not exist yet")
|
|
65
|
+
return result_ok(
|
|
66
|
+
loc.t("Nothing to index", "索引化対象なし"),
|
|
67
|
+
loc.t("There are no memos yet.", "まだメモがありません"),
|
|
68
|
+
{"added": 0, "skipped": 0, "total": 0, "collection": collection_name},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
ef = SentenceTransformerEmbeddingFunction(model_name=EMBED_MODEL)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
ctx.log.error(f"memo-index: failed to load embedder: {e}")
|
|
75
|
+
return result_error(
|
|
76
|
+
loc.t("Could not initialize embedding model", "embedding モデルを初期化できません"),
|
|
77
|
+
loc.t(f"{e}. Check that sentence-transformers is installed.", f"{e}. sentence-transformers のインストールを確認してください"),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
client = chromadb.PersistentClient(path=str(chroma_path))
|
|
81
|
+
col = client.get_or_create_collection(name=collection_name, embedding_function=ef)
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
existing_ids = set(col.get()["ids"])
|
|
85
|
+
except Exception:
|
|
86
|
+
existing_ids = set()
|
|
87
|
+
|
|
88
|
+
since = parse_iso(since_str) if since_str else None
|
|
89
|
+
total = added = skipped = 0
|
|
90
|
+
documents, ids, metadatas = [], [], []
|
|
91
|
+
|
|
92
|
+
for md in sorted(notes_root.rglob("*.md")):
|
|
93
|
+
try:
|
|
94
|
+
relative = md.relative_to(notes_root)
|
|
95
|
+
except ValueError:
|
|
96
|
+
continue
|
|
97
|
+
# reports サブディレクトリは出力先なので索引対象外
|
|
98
|
+
if "reports" in relative.parts:
|
|
99
|
+
continue
|
|
100
|
+
try:
|
|
101
|
+
lines = md.read_text(encoding="utf-8", errors="ignore").splitlines()
|
|
102
|
+
except OSError:
|
|
103
|
+
continue
|
|
104
|
+
for line_no, raw in enumerate(lines, start=1):
|
|
105
|
+
text = raw.strip()
|
|
106
|
+
if not text.startswith("- "):
|
|
107
|
+
continue
|
|
108
|
+
body = text[2:].strip()
|
|
109
|
+
if not body:
|
|
110
|
+
continue
|
|
111
|
+
first, _, rest = body.partition(" ")
|
|
112
|
+
ts = parse_iso(first)
|
|
113
|
+
if ts and rest:
|
|
114
|
+
body = rest.strip()
|
|
115
|
+
if since and ts and ts < since:
|
|
116
|
+
continue
|
|
117
|
+
total += 1
|
|
118
|
+
file_rel = str(relative)
|
|
119
|
+
doc_id = sha1(f"{file_rel}:{line_no}:{body}")
|
|
120
|
+
if doc_id in existing_ids:
|
|
121
|
+
skipped += 1
|
|
122
|
+
continue
|
|
123
|
+
documents.append(body)
|
|
124
|
+
ids.append(doc_id)
|
|
125
|
+
metadatas.append(
|
|
126
|
+
{
|
|
127
|
+
"file": file_rel,
|
|
128
|
+
"line": line_no,
|
|
129
|
+
"timestamp": ts.isoformat() if ts else "",
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
added += 1
|
|
133
|
+
|
|
134
|
+
if documents:
|
|
135
|
+
ctx.log.info(f"memo-index: adding {len(documents)} new chunks to {collection_name}")
|
|
136
|
+
col.add(documents=documents, ids=ids, metadatas=metadatas)
|
|
137
|
+
|
|
138
|
+
summary = loc.t(f"Added {added} / skipped {skipped} / found {total}", f"追加 {added} / スキップ {skipped} / 検出 {total}")
|
|
139
|
+
return result_ok(
|
|
140
|
+
loc.t(f"Indexed {added} entries", f"{added}件を索引化しました"),
|
|
141
|
+
summary,
|
|
142
|
+
{"added": added, "skipped": skipped, "total": total, "collection": collection_name},
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def sha1(value):
|
|
147
|
+
return hashlib.sha1(value.encode("utf-8")).hexdigest()
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def parse_iso(value):
|
|
151
|
+
if not value:
|
|
152
|
+
return None
|
|
153
|
+
try:
|
|
154
|
+
return datetime.fromisoformat(str(value).replace("Z", "+00:00"))
|
|
155
|
+
except ValueError:
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def result_ok(title, summary, data):
|
|
160
|
+
return {
|
|
161
|
+
"status": "ok",
|
|
162
|
+
"title": title,
|
|
163
|
+
"summary": summary,
|
|
164
|
+
"outputs": {},
|
|
165
|
+
"data": data,
|
|
166
|
+
"suggestions": [],
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def result_error(title, summary):
|
|
171
|
+
return {
|
|
172
|
+
"status": "error",
|
|
173
|
+
"title": title,
|
|
174
|
+
"summary": summary,
|
|
175
|
+
"outputs": {},
|
|
176
|
+
"data": {},
|
|
177
|
+
"suggestions": [],
|
|
178
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Builtin: memo-index
|
|
2
|
+
# notes_dir 配下のメモ行を Chroma ベクター DB に索引化する。
|
|
3
|
+
# 索引は ~/.agent-sin/index/local-index/chroma に永続化、collection は memo (デフォルト)。
|
|
4
|
+
# 多言語対応のため SentenceTransformer (paraphrase-multilingual-MiniLM-L12-v2) を使う。
|
|
5
|
+
# Python 依存: chromadb, sentence-transformers
|
|
6
|
+
# 推奨インストール先: ~/.agent-sin/.venv (auto-detect される)
|
|
7
|
+
# python3 -m venv ~/.agent-sin/.venv
|
|
8
|
+
# ~/.agent-sin/.venv/bin/pip install chromadb sentence-transformers
|
|
9
|
+
|
|
10
|
+
id: memo-index
|
|
11
|
+
name: Memo Index
|
|
12
|
+
name_i18n:
|
|
13
|
+
en: Memo Index
|
|
14
|
+
ja: メモ索引化
|
|
15
|
+
description: notes_dirのメモをChromaベクターDBに索引化する
|
|
16
|
+
description_i18n:
|
|
17
|
+
en: Index notes_dir memos into a Chroma vector database
|
|
18
|
+
ja: notes_dirのメモをChromaベクターDBに索引化する
|
|
19
|
+
runtime: python
|
|
20
|
+
|
|
21
|
+
invocation:
|
|
22
|
+
command: memo.index
|
|
23
|
+
phrases:
|
|
24
|
+
- メモを索引化
|
|
25
|
+
- インデックス更新
|
|
26
|
+
phrases_i18n:
|
|
27
|
+
en:
|
|
28
|
+
- index memos
|
|
29
|
+
- update memo index
|
|
30
|
+
ja:
|
|
31
|
+
- メモを索引化
|
|
32
|
+
- インデックス更新
|
|
33
|
+
|
|
34
|
+
input:
|
|
35
|
+
schema:
|
|
36
|
+
type: object
|
|
37
|
+
additionalProperties: false
|
|
38
|
+
properties:
|
|
39
|
+
since:
|
|
40
|
+
type: string
|
|
41
|
+
description: ISO8601 (このタイムスタンプより新しい行のみ索引化)
|
|
42
|
+
description_i18n:
|
|
43
|
+
en: ISO8601 timestamp; only newer lines are indexed
|
|
44
|
+
ja: ISO8601 (このタイムスタンプより新しい行のみ索引化)
|
|
45
|
+
collection:
|
|
46
|
+
type: string
|
|
47
|
+
default: memo
|
|
48
|
+
required: []
|
|
49
|
+
|
|
50
|
+
memory:
|
|
51
|
+
namespace: memo-index
|
|
52
|
+
read: true
|
|
53
|
+
write: true
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Builtin: memo-save
|
|
2
|
+
|
|
3
|
+
入力 text を Markdown メモのデイリーファイルに追記する。複数行に対応し、
|
|
4
|
+
1メモ = 1バレット + 継続行(2スペースインデント)の Markdown リスト形式で書き出す。
|
|
5
|
+
Runtime が outputs.note を skill.yaml の outputs[id=note] に従って保存するため、
|
|
6
|
+
このスキル自身は **content を返すだけ**でファイルには触らない。
|
|
7
|
+
|
|
8
|
+
入力:
|
|
9
|
+
args.text: 保存する本文 (必須, 改行可)
|
|
10
|
+
出力:
|
|
11
|
+
outputs.note: 追記用の Markdown。保存先の表示は skill.yaml 側で抑制する。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
|
|
20
|
+
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_shared"))
|
|
21
|
+
from i18n import localizer # noqa: E402
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def run(ctx, input):
|
|
25
|
+
loc = localizer(input)
|
|
26
|
+
args = input.get("args", {})
|
|
27
|
+
text = str(args.get("text", "")).strip()
|
|
28
|
+
|
|
29
|
+
if not text:
|
|
30
|
+
ctx.log.warn("memo-save: empty text, skipping")
|
|
31
|
+
return {
|
|
32
|
+
"status": "skipped",
|
|
33
|
+
"title": loc.t("No memo", "メモなし"),
|
|
34
|
+
"summary": loc.t("There is no text to save.", "保存する本文がありません"),
|
|
35
|
+
"outputs": {},
|
|
36
|
+
"data": {},
|
|
37
|
+
"suggestions": [],
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
ctx.log.info(f"memo-save: saving {len(text)} chars")
|
|
41
|
+
timestamp = input.get("trigger", {}).get("time") or datetime.now().isoformat()
|
|
42
|
+
content = format_memo(timestamp, text)
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
"status": "ok",
|
|
46
|
+
"title": loc.t("Saved", "保存しました"),
|
|
47
|
+
"summary": loc.t("Memo saved.", "メモを保存しました"),
|
|
48
|
+
"outputs": {
|
|
49
|
+
"note": {
|
|
50
|
+
"content": content,
|
|
51
|
+
"frontmatter": {
|
|
52
|
+
"tags": ["memo"]
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"data": {
|
|
57
|
+
"length": len(text)
|
|
58
|
+
},
|
|
59
|
+
"suggestions": [],
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def format_memo(timestamp, text):
|
|
64
|
+
# 改行を正規化し、空行はメモ区切りと衝突するため除外する
|
|
65
|
+
raw_lines = [line.rstrip() for line in text.replace("\r\n", "\n").split("\n")]
|
|
66
|
+
body_lines = [line for line in raw_lines if line.strip()]
|
|
67
|
+
if not body_lines:
|
|
68
|
+
return ""
|
|
69
|
+
first, *rest = body_lines
|
|
70
|
+
head = f"- {timestamp} {first}"
|
|
71
|
+
if not rest:
|
|
72
|
+
return head + "\n"
|
|
73
|
+
indented = "\n".join(f" {line}" for line in rest)
|
|
74
|
+
return f"{head}\n{indented}\n"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Builtin: memo-save
|
|
2
|
+
# 会話の skill-call または agent-sin run memo-save --text "..." で呼ばれる。
|
|
3
|
+
# Markdown のデイリーノートに追記する低リスクスキル。複数行テキストにも対応する。
|
|
4
|
+
# ユーザースキル著作のリファレンスとしても読まれる。詳細は https://agent.shingoirie.com/skill-authoring を参照。
|
|
5
|
+
|
|
6
|
+
id: memo-save
|
|
7
|
+
name: Memo Save
|
|
8
|
+
name_i18n:
|
|
9
|
+
en: Memo Save
|
|
10
|
+
ja: メモ保存
|
|
11
|
+
description: 会話内容をMarkdownメモとして保存する
|
|
12
|
+
description_i18n:
|
|
13
|
+
en: Save conversation text as a Markdown memo
|
|
14
|
+
ja: 会話内容をMarkdownメモとして保存する
|
|
15
|
+
runtime: python
|
|
16
|
+
side_effect: true
|
|
17
|
+
|
|
18
|
+
invocation:
|
|
19
|
+
command: memo.save
|
|
20
|
+
phrases:
|
|
21
|
+
- メモしといて
|
|
22
|
+
- メモして
|
|
23
|
+
phrases_i18n:
|
|
24
|
+
en:
|
|
25
|
+
- save this memo
|
|
26
|
+
- take a memo
|
|
27
|
+
- remember this
|
|
28
|
+
ja:
|
|
29
|
+
- メモしといて
|
|
30
|
+
- メモして
|
|
31
|
+
|
|
32
|
+
input:
|
|
33
|
+
schema:
|
|
34
|
+
type: object
|
|
35
|
+
properties:
|
|
36
|
+
text:
|
|
37
|
+
type: string
|
|
38
|
+
required:
|
|
39
|
+
- text
|
|
40
|
+
|
|
41
|
+
outputs:
|
|
42
|
+
- id: note
|
|
43
|
+
type: markdown
|
|
44
|
+
path: notes/{{yyyy}}/{{MM}}
|
|
45
|
+
filename: "{{date}}.md"
|
|
46
|
+
append: true
|
|
47
|
+
show_saved: false
|
|
48
|
+
|
|
49
|
+
memory:
|
|
50
|
+
namespace: memo
|
|
51
|
+
read: true
|
|
52
|
+
write: true
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Builtin: memo-search
|
|
2
|
+
|
|
3
|
+
input.sources.notes_dir 配下の *.md を全文走査し、query にすべての語が含まれる
|
|
4
|
+
最初の 1 行を結果に含める。outputs は持たず data.matches に結果を返すのみ。
|
|
5
|
+
|
|
6
|
+
入力:
|
|
7
|
+
args.query: 検索語 (空白区切りで AND 検索)
|
|
8
|
+
args.limit: 上限 (1..50, default 10)
|
|
9
|
+
出力:
|
|
10
|
+
data.matches: [{file, line, text}]
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_shared"))
|
|
20
|
+
from i18n import localizer # noqa: E402
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def run(ctx, input):
|
|
24
|
+
loc = localizer(input)
|
|
25
|
+
args = input.get("args", {})
|
|
26
|
+
query = str(args.get("query", "")).strip()
|
|
27
|
+
limit = int(args.get("limit", 10))
|
|
28
|
+
notes_dir = input.get("sources", {}).get("notes_dir")
|
|
29
|
+
|
|
30
|
+
if not query:
|
|
31
|
+
ctx.log.warn("memo-search: empty query")
|
|
32
|
+
return result("skipped", loc.t("No query", "検索語なし"), loc.t("There is no keyword to search.", "検索するキーワードがありません"), [])
|
|
33
|
+
if not notes_dir:
|
|
34
|
+
ctx.log.error("memo-search: notes_dir not provided by runtime")
|
|
35
|
+
return result("error", loc.t("Cannot search", "検索できません"), loc.t("The notes directory was not provided by the runtime.", "ノート保存先がRuntimeから渡されていません"), [])
|
|
36
|
+
|
|
37
|
+
root = Path(notes_dir).expanduser().resolve()
|
|
38
|
+
if not root.exists():
|
|
39
|
+
ctx.log.info(f"memo-search: notes_dir does not exist yet: {root}")
|
|
40
|
+
return result("ok", loc.t("0 matches", "0件見つかりました"), loc.t("There are no memos yet.", "まだメモがありません"), [])
|
|
41
|
+
|
|
42
|
+
terms = [term.casefold() for term in query.split() if term]
|
|
43
|
+
ctx.log.info(f"memo-search: searching {len(terms)} term(s) in {root}, limit={limit}")
|
|
44
|
+
matches = search_notes(root, terms, limit)
|
|
45
|
+
if not matches:
|
|
46
|
+
return result("ok", loc.t("0 matches", "0件見つかりました"), loc.t(f'No memos contain "{query}".', f"「{query}」を含むメモは見つかりませんでした"), [])
|
|
47
|
+
|
|
48
|
+
lines = [loc.t("Found memos:", "見つかったメモ:")]
|
|
49
|
+
for item in matches:
|
|
50
|
+
lines.append(f"- {item['file']}:{item['line']} {item['text']}")
|
|
51
|
+
return result("ok", loc.t(f"{len(matches)} matches", f"{len(matches)}件見つかりました"), "\n".join(lines), matches)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def search_notes(root, terms, limit):
|
|
55
|
+
matches = []
|
|
56
|
+
for file in sorted(root.rglob("*.md")):
|
|
57
|
+
if len(matches) >= limit:
|
|
58
|
+
break
|
|
59
|
+
try:
|
|
60
|
+
resolved = file.resolve()
|
|
61
|
+
if root != resolved and root not in resolved.parents:
|
|
62
|
+
continue
|
|
63
|
+
lines = file.read_text(encoding="utf-8", errors="ignore").splitlines()
|
|
64
|
+
except OSError:
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
for index, line in enumerate(lines, start=1):
|
|
68
|
+
if all(term in line.casefold() for term in terms):
|
|
69
|
+
matches.append(
|
|
70
|
+
{
|
|
71
|
+
"file": str(file.relative_to(root)),
|
|
72
|
+
"line": index,
|
|
73
|
+
"text": snippet(line),
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
break
|
|
77
|
+
return matches
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def snippet(line):
|
|
81
|
+
text = " ".join(line.strip().split())
|
|
82
|
+
if len(text) <= 160:
|
|
83
|
+
return text
|
|
84
|
+
return f"{text[:157]}..."
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def result(status, title, summary, matches):
|
|
88
|
+
return {
|
|
89
|
+
"status": status,
|
|
90
|
+
"title": title,
|
|
91
|
+
"summary": summary,
|
|
92
|
+
"outputs": {},
|
|
93
|
+
"data": {
|
|
94
|
+
"matches": matches,
|
|
95
|
+
},
|
|
96
|
+
"suggestions": [],
|
|
97
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Builtin: memo-search
|
|
2
|
+
# notes_dir 配下の Markdown を全文 grep する読み取り専用スキル。
|
|
3
|
+
# memory namespace は memo (memo-save と共有) だが write=false で読み出しのみ。
|
|
4
|
+
|
|
5
|
+
id: memo-search
|
|
6
|
+
name: Memo Search
|
|
7
|
+
name_i18n:
|
|
8
|
+
en: Memo Search
|
|
9
|
+
ja: メモ検索
|
|
10
|
+
description: 保存済みMarkdownメモをキーワード検索する
|
|
11
|
+
description_i18n:
|
|
12
|
+
en: Search saved Markdown memos by keyword
|
|
13
|
+
ja: 保存済みMarkdownメモをキーワード検索する
|
|
14
|
+
runtime: python
|
|
15
|
+
|
|
16
|
+
invocation:
|
|
17
|
+
command: memo.search
|
|
18
|
+
phrases:
|
|
19
|
+
- メモ検索
|
|
20
|
+
- メモを検索して
|
|
21
|
+
- 検索して
|
|
22
|
+
phrases_i18n:
|
|
23
|
+
en:
|
|
24
|
+
- search memos
|
|
25
|
+
- search my notes
|
|
26
|
+
- find a memo
|
|
27
|
+
ja:
|
|
28
|
+
- メモ検索
|
|
29
|
+
- メモを検索して
|
|
30
|
+
- 検索して
|
|
31
|
+
|
|
32
|
+
input:
|
|
33
|
+
schema:
|
|
34
|
+
type: object
|
|
35
|
+
additionalProperties: false
|
|
36
|
+
properties:
|
|
37
|
+
query:
|
|
38
|
+
type: string
|
|
39
|
+
minLength: 1
|
|
40
|
+
limit:
|
|
41
|
+
type: integer
|
|
42
|
+
minimum: 1
|
|
43
|
+
maximum: 50
|
|
44
|
+
default: 10
|
|
45
|
+
required:
|
|
46
|
+
- query
|
|
47
|
+
|
|
48
|
+
memory:
|
|
49
|
+
namespace: memo
|
|
50
|
+
read: true
|
|
51
|
+
write: false
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Builtin: memo-vector-search
|
|
2
|
+
|
|
3
|
+
memo-index で索引化した Chroma collection に対してクエリを embedding 化し、
|
|
4
|
+
意味的に近いメモを top-K で返す。先に agent-sin run memo-index を実行する必要あり。
|
|
5
|
+
|
|
6
|
+
入力:
|
|
7
|
+
args.query: 検索クエリ (必須, 1 文字以上)
|
|
8
|
+
args.limit: 上位件数 (1..50, default 5)
|
|
9
|
+
args.collection: 索引コレクション名 (default: memo)
|
|
10
|
+
|
|
11
|
+
出力:
|
|
12
|
+
data.matches: [{text, file, line, timestamp, distance}]
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_shared"))
|
|
22
|
+
from i18n import localizer # noqa: E402
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
EMBED_MODEL = "paraphrase-multilingual-MiniLM-L12-v2"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def run(ctx, input):
|
|
29
|
+
loc = localizer(input)
|
|
30
|
+
args = input.get("args", {})
|
|
31
|
+
query = str(args.get("query", "")).strip()
|
|
32
|
+
limit = int(args.get("limit", 5))
|
|
33
|
+
collection_name = args.get("collection", "memo")
|
|
34
|
+
sources = input.get("sources", {})
|
|
35
|
+
index_dir = sources.get("index_dir")
|
|
36
|
+
|
|
37
|
+
if not query:
|
|
38
|
+
ctx.log.warn("memo-vector-search: empty query")
|
|
39
|
+
return result_value("skipped", loc.t("No query", "クエリなし"), loc.t("There is no keyword to search.", "検索するキーワードがありません"), [])
|
|
40
|
+
if not index_dir:
|
|
41
|
+
ctx.log.error("memo-vector-search: index_dir not provided")
|
|
42
|
+
return result_value("error", loc.t("Cannot search", "検索できません"), loc.t("index_dir was not provided.", "index_dir が渡されていません"), [])
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
import chromadb
|
|
46
|
+
from chromadb.utils.embedding_functions import (
|
|
47
|
+
SentenceTransformerEmbeddingFunction,
|
|
48
|
+
)
|
|
49
|
+
except ImportError as e:
|
|
50
|
+
ctx.log.error(f"memo-vector-search: missing dependency: {e}")
|
|
51
|
+
return result_value(
|
|
52
|
+
"error",
|
|
53
|
+
loc.t("chromadb not found", "chromadb が見つかりません"),
|
|
54
|
+
loc.t(
|
|
55
|
+
"Run: python3 -m venv ~/.agent-sin/.venv && ~/.agent-sin/.venv/bin/pip install chromadb sentence-transformers",
|
|
56
|
+
"次を実行: python3 -m venv ~/.agent-sin/.venv && ~/.agent-sin/.venv/bin/pip install chromadb sentence-transformers",
|
|
57
|
+
),
|
|
58
|
+
[],
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
chroma_path = Path(index_dir).expanduser().resolve() / "local-index" / "chroma"
|
|
62
|
+
if not chroma_path.exists():
|
|
63
|
+
return result_value(
|
|
64
|
+
"ok",
|
|
65
|
+
loc.t("0 matches", "0件"),
|
|
66
|
+
loc.t("The index is empty. Run agent-sin run memo-index first.", "索引が空です。先に agent-sin run memo-index を実行してください"),
|
|
67
|
+
[],
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
ctx.log.info(f"memo-vector-search: query='{query[:40]}' limit={limit}")
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
ef = SentenceTransformerEmbeddingFunction(model_name=EMBED_MODEL)
|
|
74
|
+
client = chromadb.PersistentClient(path=str(chroma_path))
|
|
75
|
+
col = client.get_or_create_collection(
|
|
76
|
+
name=collection_name, embedding_function=ef
|
|
77
|
+
)
|
|
78
|
+
r = col.query(query_texts=[query], n_results=limit)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
ctx.log.error(f"memo-vector-search: query failed: {e}")
|
|
81
|
+
return result_value("error", loc.t("Search error", "検索エラー"), str(e), [])
|
|
82
|
+
|
|
83
|
+
matches = []
|
|
84
|
+
docs = (r.get("documents") or [[]])[0]
|
|
85
|
+
metas = (r.get("metadatas") or [[]])[0]
|
|
86
|
+
dists = (r.get("distances") or [[]])[0]
|
|
87
|
+
for doc, meta, dist in zip(docs, metas, dists):
|
|
88
|
+
meta = meta or {}
|
|
89
|
+
matches.append(
|
|
90
|
+
{
|
|
91
|
+
"text": doc,
|
|
92
|
+
"file": meta.get("file", ""),
|
|
93
|
+
"line": meta.get("line", 0),
|
|
94
|
+
"timestamp": meta.get("timestamp", ""),
|
|
95
|
+
"distance": float(dist),
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if not matches:
|
|
100
|
+
return result_value(
|
|
101
|
+
"ok", loc.t("0 matches", "0件"), loc.t(f'No memos are close to "{query}".', f"「{query}」に近いメモは見つかりませんでした"), []
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
lines = [loc.t("Closest memos:", "近いメモ:")]
|
|
105
|
+
for i, m in enumerate(matches, start=1):
|
|
106
|
+
lines.append(f" {i}) {m['file']}:{m['line']} dist={m['distance']:.3f}")
|
|
107
|
+
lines.append(f" {m['text'][:80]}")
|
|
108
|
+
return result_value(
|
|
109
|
+
"ok", loc.t(f"{len(matches)} matches", f"{len(matches)}件見つかりました"), "\n".join(lines), matches
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def result_value(status, title, summary, matches):
|
|
114
|
+
return {
|
|
115
|
+
"status": status,
|
|
116
|
+
"title": title,
|
|
117
|
+
"summary": summary,
|
|
118
|
+
"outputs": {},
|
|
119
|
+
"data": {"matches": matches},
|
|
120
|
+
"suggestions": [],
|
|
121
|
+
}
|