agent-sin 0.1.12 → 0.1.16
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 +79 -0
- package/README.md +2 -1
- package/builtin-skills/_shared/_todo_lib.py +290 -0
- package/builtin-skills/even-g2-setup/main.ts +896 -0
- package/builtin-skills/even-g2-setup/skill.yaml +133 -0
- package/builtin-skills/memo-delete/main.py +28 -107
- package/builtin-skills/memo-delete/skill.yaml +10 -21
- package/builtin-skills/memo-index/main.py +96 -64
- package/builtin-skills/memo-index/skill.yaml +4 -10
- package/builtin-skills/memo-list/main.py +126 -72
- package/builtin-skills/memo-list/skill.yaml +8 -14
- package/builtin-skills/memo-save/main.py +191 -25
- package/builtin-skills/memo-save/skill.yaml +29 -5
- package/builtin-skills/memo-search/main.py +38 -18
- package/builtin-skills/memo-vector-search/main.py +11 -6
- package/builtin-skills/nightly-topic-knowledge/_feedback_lib.py +391 -0
- package/builtin-skills/nightly-topic-knowledge/_topics_lib.py +415 -0
- package/builtin-skills/nightly-topic-knowledge/main.py +403 -0
- package/builtin-skills/nightly-topic-knowledge/skill.yaml +88 -0
- package/builtin-skills/schedule-add/main.py +26 -0
- package/builtin-skills/service-restart/main.ts +249 -0
- package/builtin-skills/service-restart/skill.yaml +49 -0
- package/builtin-skills/todo-add/main.py +3 -1
- package/builtin-skills/todo-delete/main.py +3 -1
- package/builtin-skills/todo-done/main.py +3 -1
- package/builtin-skills/todo-list/main.py +4 -1
- package/builtin-skills/todo-tick/main.py +3 -1
- package/builtin-skills/topic-knowledge-read/main.py +118 -0
- package/builtin-skills/topic-knowledge-read/skill.yaml +49 -0
- package/dist/builder/build-action-classifier.d.ts +18 -0
- package/dist/builder/build-action-classifier.js +82 -1
- package/dist/builder/build-flow.d.ts +33 -4
- package/dist/builder/build-flow.js +251 -89
- package/dist/builder/builder-session.d.ts +1 -1
- package/dist/builder/builder-session.js +112 -7
- package/dist/builder/conversation-router.d.ts +4 -2
- package/dist/builder/conversation-router.js +19 -2
- package/dist/cli/index.js +323 -20
- package/dist/core/ai-provider.d.ts +1 -0
- package/dist/core/ai-provider.js +8 -3
- package/dist/core/chat-engine.d.ts +9 -3
- package/dist/core/chat-engine.js +1263 -146
- package/dist/core/config.d.ts +4 -0
- package/dist/core/config.js +82 -0
- package/dist/core/daily-memory-promotion.d.ts +7 -0
- package/dist/core/daily-memory-promotion.js +596 -18
- package/dist/core/image-attachments.d.ts +31 -0
- package/dist/core/image-attachments.js +237 -0
- package/dist/core/logger.d.ts +2 -1
- package/dist/core/logger.js +77 -1
- package/dist/core/memo-migration.d.ts +3 -0
- package/dist/core/memo-migration.js +422 -0
- package/dist/core/native-modules.d.ts +24 -0
- package/dist/core/native-modules.js +99 -0
- package/dist/core/notifier.d.ts +8 -3
- package/dist/core/notifier.js +191 -17
- package/dist/core/obsidian-vault.d.ts +19 -0
- package/dist/core/obsidian-vault.js +477 -0
- package/dist/core/operating-model.d.ts +2 -0
- package/dist/core/operating-model.js +15 -0
- package/dist/core/output-writer.d.ts +3 -2
- package/dist/core/output-writer.js +108 -7
- package/dist/core/profile-memory.js +22 -1
- package/dist/core/runtime.d.ts +2 -0
- package/dist/core/runtime.js +9 -1
- package/dist/core/secrets.d.ts +4 -0
- package/dist/core/secrets.js +34 -0
- package/dist/core/skill-history.d.ts +44 -0
- package/dist/core/skill-history.js +329 -0
- package/dist/core/skill-registry.d.ts +5 -0
- package/dist/core/skill-registry.js +11 -0
- package/dist/discord/bot.d.ts +1 -0
- package/dist/discord/bot.js +181 -10
- package/dist/even-g2/gateway.d.ts +15 -0
- package/dist/even-g2/gateway.js +868 -0
- package/dist/runtimes/codex-app-server.d.ts +5 -1
- package/dist/runtimes/codex-app-server.js +147 -8
- package/dist/runtimes/python-runner.js +82 -0
- package/dist/runtimes/typescript-runner.js +13 -1
- package/dist/skills-sdk/types.d.ts +19 -4
- package/dist/telegram/bot.d.ts +1 -0
- package/dist/telegram/bot.js +115 -7
- package/package.json +3 -1
- package/templates/even-g2-agent/README.md +83 -0
- package/templates/even-g2-agent/app.json +20 -0
- package/templates/even-g2-agent/index.html +31 -0
- package/templates/even-g2-agent/package-lock.json +1836 -0
- package/templates/even-g2-agent/package.json +22 -0
- package/templates/even-g2-agent/scripts/qr-auto.mjs +182 -0
- package/templates/even-g2-agent/src/embedded-config.ts +4 -0
- package/templates/even-g2-agent/src/main.ts +539 -0
- package/templates/even-g2-agent/src/style.css +70 -0
- package/templates/even-g2-agent/tsconfig.json +11 -0
- package/templates/skill-python/main.py +20 -2
- package/templates/skill-python/skill.yaml +9 -0
- package/templates/skill-typescript/main.ts +40 -5
- package/templates/skill-typescript/skill.yaml +9 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Builtin: even-g2-setup
|
|
2
|
+
# Even G2 メガネ用のセットアップを一括で行う。.env 保存 / app.json whitelist 更新 / .ehpk ビルドまで。
|
|
3
|
+
|
|
4
|
+
id: even-g2-setup
|
|
5
|
+
name: Even G2 Setup
|
|
6
|
+
name_i18n:
|
|
7
|
+
en: Even G2 Setup
|
|
8
|
+
ja: Even G2 セットアップ
|
|
9
|
+
description: Set up Even G2 glasses end-to-end (gateway env, whitelist, .ehpk build).
|
|
10
|
+
description_i18n:
|
|
11
|
+
en: Set up Even G2 glasses end-to-end (gateway env, whitelist, .ehpk build).
|
|
12
|
+
ja: Even G2 メガネのセットアップ(gateway 設定・whitelist 更新・.ehpk ビルド)を一括で行う
|
|
13
|
+
runtime: typescript
|
|
14
|
+
entry: main.ts
|
|
15
|
+
handler: run
|
|
16
|
+
output_mode: raw
|
|
17
|
+
side_effect: true
|
|
18
|
+
|
|
19
|
+
invocation:
|
|
20
|
+
command: even.g2.setup
|
|
21
|
+
phrases:
|
|
22
|
+
- G2のセットアップして
|
|
23
|
+
- Even G2をセットアップして
|
|
24
|
+
- G2を使えるようにして
|
|
25
|
+
- メガネ用のアプリを作って
|
|
26
|
+
- G2のehpkを作って
|
|
27
|
+
phrases_i18n:
|
|
28
|
+
en:
|
|
29
|
+
- set up even g2
|
|
30
|
+
- setup g2 glasses
|
|
31
|
+
- build the g2 ehpk
|
|
32
|
+
- prepare even g2 app
|
|
33
|
+
ja:
|
|
34
|
+
- G2のセットアップして
|
|
35
|
+
- Even G2をセットアップして
|
|
36
|
+
- G2を使えるようにして
|
|
37
|
+
- メガネ用のアプリを作って
|
|
38
|
+
- G2のehpkを作って
|
|
39
|
+
|
|
40
|
+
input:
|
|
41
|
+
schema:
|
|
42
|
+
type: object
|
|
43
|
+
additionalProperties: false
|
|
44
|
+
properties:
|
|
45
|
+
package_id:
|
|
46
|
+
type: string
|
|
47
|
+
pattern: "^[a-z][a-z0-9]*(\\.[a-z][a-z0-9]*)+$"
|
|
48
|
+
description: Reverse-DNS package id written into app.json (e.g. com.you.agentsin). Defaults to existing value, then a value derived from git user.name, then com.example.agentsin.
|
|
49
|
+
description_i18n:
|
|
50
|
+
en: Reverse-DNS package id written into app.json (e.g. com.you.agentsin). Defaults to existing value, then a value derived from git user.name, then com.example.agentsin.
|
|
51
|
+
ja: app.json に書き込む reverse-DNS のパッケージ ID(例 com.you.agentsin)。既存値 → git user.name 由来 → com.example.agentsin の順でフォールバック
|
|
52
|
+
history_channel:
|
|
53
|
+
type: string
|
|
54
|
+
enum: [discord, telegram, none, auto]
|
|
55
|
+
description: Where to mirror G2 conversation history.
|
|
56
|
+
description_i18n:
|
|
57
|
+
en: Where to mirror G2 conversation history.
|
|
58
|
+
ja: G2 の会話履歴のミラー先(discord/telegram/none/auto)
|
|
59
|
+
discord_thread:
|
|
60
|
+
type: string
|
|
61
|
+
pattern: "^(auto|off|[0-9]+)$"
|
|
62
|
+
description: "Discord thread handling: auto, off, or numeric thread id."
|
|
63
|
+
description_i18n:
|
|
64
|
+
en: "Discord thread handling: auto, off, or numeric thread id."
|
|
65
|
+
ja: Discord スレッドの扱い(auto/off/スレッドID)
|
|
66
|
+
host:
|
|
67
|
+
type: string
|
|
68
|
+
description: Gateway bind host (defaults to 0.0.0.0).
|
|
69
|
+
description_i18n:
|
|
70
|
+
en: Gateway bind host (defaults to 0.0.0.0).
|
|
71
|
+
ja: gateway のバインドホスト(既定は 0.0.0.0)
|
|
72
|
+
port:
|
|
73
|
+
type: integer
|
|
74
|
+
minimum: 1
|
|
75
|
+
maximum: 65535
|
|
76
|
+
description: Gateway port (defaults to 8765).
|
|
77
|
+
description_i18n:
|
|
78
|
+
en: Gateway port (defaults to 8765).
|
|
79
|
+
ja: gateway のポート(既定は 8765)
|
|
80
|
+
token:
|
|
81
|
+
type: string
|
|
82
|
+
minLength: 8
|
|
83
|
+
description: Gateway token. Auto-generated when omitted.
|
|
84
|
+
description_i18n:
|
|
85
|
+
en: Gateway token. Auto-generated when omitted.
|
|
86
|
+
ja: gateway トークン。省略時は自動生成
|
|
87
|
+
host_lan_ip:
|
|
88
|
+
type: string
|
|
89
|
+
description: This machine's LAN IP added to the whitelist. Auto-detected if omitted.
|
|
90
|
+
description_i18n:
|
|
91
|
+
en: This machine's LAN IP added to the whitelist. Auto-detected if omitted.
|
|
92
|
+
ja: 母艦のLAN IP。省略時は自動検出
|
|
93
|
+
tailscale_host:
|
|
94
|
+
type: string
|
|
95
|
+
description: Tailscale MagicDNS host to add to the whitelist (no scheme, no port).
|
|
96
|
+
description_i18n:
|
|
97
|
+
en: Tailscale MagicDNS host to add to the whitelist (no scheme, no port).
|
|
98
|
+
ja: whitelist に追加する Tailscale MagicDNS ホスト
|
|
99
|
+
server_url:
|
|
100
|
+
type: string
|
|
101
|
+
description: Explicit gateway URL to embed in the .ehpk (overrides auto-derivation). Use this when fronting the gateway via Tailscale Serve or another HTTPS proxy. Also added to the whitelist.
|
|
102
|
+
description_i18n:
|
|
103
|
+
en: Explicit gateway URL to embed in the .ehpk (overrides auto-derivation). Use this when fronting the gateway via Tailscale Serve or another HTTPS proxy. Also added to the whitelist.
|
|
104
|
+
ja: .ehpk に埋め込む gateway URL を明示指定(自動導出を上書き)。Tailscale Serve など HTTPS プロキシ経由で公開している場合に使う。whitelist にも自動追加されます
|
|
105
|
+
extra_hosts:
|
|
106
|
+
type: array
|
|
107
|
+
items:
|
|
108
|
+
type: string
|
|
109
|
+
description: Additional host[:port] entries to add to the whitelist.
|
|
110
|
+
description_i18n:
|
|
111
|
+
en: Additional host[:port] entries to add to the whitelist.
|
|
112
|
+
ja: whitelist に追加したい host[:port] のリスト
|
|
113
|
+
skip_install:
|
|
114
|
+
type: boolean
|
|
115
|
+
default: false
|
|
116
|
+
description: Skip npm install even when node_modules is missing.
|
|
117
|
+
description_i18n:
|
|
118
|
+
en: Skip npm install even when node_modules is missing.
|
|
119
|
+
ja: node_modules が無くても npm install をスキップする
|
|
120
|
+
skip_pack:
|
|
121
|
+
type: boolean
|
|
122
|
+
default: false
|
|
123
|
+
description: Only write .env and app.json; do not build the .ehpk.
|
|
124
|
+
description_i18n:
|
|
125
|
+
en: Only write .env and app.json; do not build the .ehpk.
|
|
126
|
+
ja: .env と app.json の更新だけ行い、.ehpk のビルドはスキップ
|
|
127
|
+
force:
|
|
128
|
+
type: boolean
|
|
129
|
+
default: false
|
|
130
|
+
description: Re-copy template files even when the workspace copy already exists.
|
|
131
|
+
description_i18n:
|
|
132
|
+
en: Re-copy template files even when the workspace copy already exists.
|
|
133
|
+
ja: ワークスペースに既存のコピーがあっても、テンプレを再コピーする
|
|
@@ -1,149 +1,70 @@
|
|
|
1
1
|
"""Builtin: memo-delete
|
|
2
2
|
|
|
3
|
-
memo
|
|
4
|
-
|
|
3
|
+
notes/memo/{title}.md を削除する。
|
|
4
|
+
- title が完全一致するファイルがあれば削除
|
|
5
|
+
- 無ければファイル名に部分一致するものを探し、1件なら削除、複数なら曖昧と判定
|
|
5
6
|
"""
|
|
6
7
|
|
|
7
8
|
from __future__ import annotations
|
|
8
9
|
|
|
9
10
|
import os
|
|
10
|
-
import re
|
|
11
11
|
import sys
|
|
12
|
-
import
|
|
13
|
-
from datetime import datetime
|
|
12
|
+
from pathlib import Path
|
|
14
13
|
|
|
15
14
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_shared"))
|
|
16
15
|
from i18n import localizer # noqa: E402
|
|
17
16
|
|
|
18
17
|
|
|
19
|
-
_DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
|
|
20
|
-
_MEMO_LINE_RE = re.compile(r"^-\s+")
|
|
21
|
-
_CONTINUATION_RE = re.compile(r"^ \S")
|
|
22
|
-
|
|
23
|
-
|
|
24
18
|
async def run(ctx, input):
|
|
25
19
|
loc = localizer(input)
|
|
26
20
|
args = input.get("args", {}) or {}
|
|
27
|
-
workspace = input.get("sources", {}).get("workspace", "")
|
|
28
21
|
notes_dir = input.get("sources", {}).get("notes_dir", "")
|
|
29
|
-
if not
|
|
30
|
-
return _err(loc.t("
|
|
31
|
-
|
|
32
|
-
date_str = str(args.get("date", "")).strip()
|
|
33
|
-
if not date_str:
|
|
34
|
-
date_str = datetime.now().strftime("%Y-%m-%d")
|
|
35
|
-
if not _DATE_RE.match(date_str):
|
|
36
|
-
return _err(loc.t("Invalid date", "日付不正"), loc.t("Use YYYY-MM-DD for date.", "date は YYYY-MM-DD 形式で指定してください"))
|
|
37
|
-
|
|
38
|
-
match = args.get("match")
|
|
39
|
-
match = str(match).strip() if match else None
|
|
40
|
-
index = args.get("index")
|
|
41
|
-
if index is not None and not isinstance(index, int):
|
|
42
|
-
return _err(loc.t("Invalid index", "index不正"), loc.t("index must be a positive integer.", "index は正の整数で指定してください"))
|
|
43
|
-
if not match and index is None:
|
|
44
|
-
return _err(loc.t("Cannot identify memo", "特定不可"), loc.t("Specify either match or index.", "match か index のどちらかを指定してください"))
|
|
22
|
+
if not notes_dir:
|
|
23
|
+
return _err(loc.t("Notes unavailable", "ノート不明"), loc.t("notes_dir is unavailable.", "notes_dir が取得できません"))
|
|
45
24
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return _err(loc.t("File not found", "ファイルなし"), loc.t(f"No memo file exists for {date_str}.", f"{date_str} のメモファイルがありません"))
|
|
25
|
+
title = str(args.get("title", "")).strip()
|
|
26
|
+
if not title:
|
|
27
|
+
return _err(loc.t("Title required", "タイトル必須"), loc.t("Specify the memo title.", "タイトルを指定してください"))
|
|
50
28
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
29
|
+
memo_dir = Path(notes_dir) / "memo"
|
|
30
|
+
if not memo_dir.exists():
|
|
31
|
+
return _err(loc.t("No memos", "メモなし"), loc.t("There are no memos yet.", "まだメモはありません"))
|
|
54
32
|
|
|
55
|
-
|
|
56
|
-
if
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
target_range = None
|
|
60
|
-
if index is not None:
|
|
61
|
-
if index < 1 or index > len(memo_ranges):
|
|
62
|
-
return _err(loc.t("Invalid index", "index不正"), loc.t(f"index {index} is out of range ({len(memo_ranges)} memos).", f"index {index} は範囲外です (メモ {len(memo_ranges)} 件)"))
|
|
63
|
-
target_range = memo_ranges[index - 1]
|
|
33
|
+
exact = memo_dir / f"{title}.md"
|
|
34
|
+
if exact.exists():
|
|
35
|
+
target = exact
|
|
64
36
|
else:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if any(match in lines[i] for i in range(r[0], r[1]))
|
|
68
|
-
]
|
|
37
|
+
lowered = title.lower()
|
|
38
|
+
candidates = [p for p in memo_dir.glob("*.md") if lowered in p.stem.lower()]
|
|
69
39
|
if not candidates:
|
|
70
|
-
return _err(loc.t("Not found", "見つかりません"), loc.t(f'No memo matches "{
|
|
40
|
+
return _err(loc.t("Not found", "見つかりません"), loc.t(f'No memo matches "{title}".', f'"{title}" に一致するメモがありません'))
|
|
71
41
|
if len(candidates) > 1:
|
|
72
|
-
preview = "\n".join(
|
|
73
|
-
f" {idx + 1}. {lines[start]}" for idx, (start, _end) in enumerate(candidates[:5])
|
|
74
|
-
)
|
|
42
|
+
preview = "\n".join(f" - {p.stem}" for p in candidates[:5])
|
|
75
43
|
return _err(
|
|
76
44
|
loc.t("Ambiguous match", "曖昧です"),
|
|
77
|
-
loc.t(f"{len(candidates)} memos matched. Specify
|
|
45
|
+
loc.t(f"{len(candidates)} memos matched. Specify a more exact title:\n{preview}", f"{len(candidates)} 件一致しました。より正確なタイトルで指定してください:\n{preview}"),
|
|
78
46
|
)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
start, end = target_range
|
|
82
|
-
removed_lines = lines[start:end]
|
|
83
|
-
new_lines = lines[:start] + lines[end:]
|
|
84
|
-
new_content = "\n".join(new_lines)
|
|
85
|
-
if raw.endswith("\n") and not new_content.endswith("\n"):
|
|
86
|
-
new_content += "\n"
|
|
87
|
-
if not raw.endswith("\n") and new_content.endswith("\n"):
|
|
88
|
-
new_content = new_content.rstrip("\n")
|
|
47
|
+
target = candidates[0]
|
|
89
48
|
|
|
90
49
|
try:
|
|
91
|
-
|
|
92
|
-
except
|
|
93
|
-
return _err(loc.t("
|
|
50
|
+
target.unlink()
|
|
51
|
+
except OSError as e:
|
|
52
|
+
return _err(loc.t("Delete failed", "削除失敗"), loc.t(f"Failed to delete {target}: {e}", f"{target} の削除に失敗しました: {e}"))
|
|
94
53
|
|
|
95
|
-
ctx.log.info(f"memo-delete:
|
|
96
|
-
|
|
97
|
-
preview = removed_lines[0].lstrip("- ").strip()
|
|
98
|
-
if len(preview) > 60:
|
|
99
|
-
preview = preview[:57] + "..."
|
|
100
|
-
remaining = len(memo_ranges) - 1
|
|
54
|
+
ctx.log.info(f"memo-delete: removed {target}")
|
|
101
55
|
return {
|
|
102
56
|
"status": "ok",
|
|
103
57
|
"title": loc.t("Deleted", "削除"),
|
|
104
|
-
"summary": loc.t(f"Deleted memo
|
|
58
|
+
"summary": loc.t(f"Deleted memo: {target.stem}", f"メモを削除しました: {target.stem}"),
|
|
105
59
|
"outputs": {},
|
|
106
60
|
"data": {
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"remaining_memos": remaining,
|
|
110
|
-
"path": path,
|
|
61
|
+
"title": target.stem,
|
|
62
|
+
"file": str(target.relative_to(notes_dir)),
|
|
111
63
|
},
|
|
112
64
|
"suggestions": [],
|
|
113
65
|
}
|
|
114
66
|
|
|
115
67
|
|
|
116
|
-
def _collect_memo_ranges(lines):
|
|
117
|
-
ranges = []
|
|
118
|
-
i = 0
|
|
119
|
-
while i < len(lines):
|
|
120
|
-
if _MEMO_LINE_RE.match(lines[i]):
|
|
121
|
-
start = i
|
|
122
|
-
j = i + 1
|
|
123
|
-
while j < len(lines) and _CONTINUATION_RE.match(lines[j]):
|
|
124
|
-
j += 1
|
|
125
|
-
ranges.append((start, j))
|
|
126
|
-
i = j
|
|
127
|
-
else:
|
|
128
|
-
i += 1
|
|
129
|
-
return ranges
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def _write_atomic(path, content):
|
|
133
|
-
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
134
|
-
fd, tmp = tempfile.mkstemp(prefix=".memo.", suffix=".md.tmp", dir=os.path.dirname(path))
|
|
135
|
-
try:
|
|
136
|
-
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
|
137
|
-
f.write(content)
|
|
138
|
-
os.replace(tmp, path)
|
|
139
|
-
except Exception:
|
|
140
|
-
try:
|
|
141
|
-
os.unlink(tmp)
|
|
142
|
-
except Exception:
|
|
143
|
-
pass
|
|
144
|
-
raise
|
|
145
|
-
|
|
146
|
-
|
|
147
68
|
def _err(title, summary):
|
|
148
69
|
return {
|
|
149
70
|
"status": "error",
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# Builtin: memo-delete
|
|
2
|
-
#
|
|
2
|
+
# notes/memo/{title}.md を削除する。memo-save と対称。
|
|
3
3
|
|
|
4
4
|
id: memo-delete
|
|
5
5
|
name: Memo Delete
|
|
6
6
|
name_i18n:
|
|
7
7
|
en: Memo Delete
|
|
8
8
|
ja: メモ削除
|
|
9
|
-
description:
|
|
9
|
+
description: タイトル指定でメモファイルを削除する
|
|
10
10
|
description_i18n:
|
|
11
|
-
en: Delete a memo
|
|
12
|
-
ja:
|
|
11
|
+
en: Delete a memo file by title
|
|
12
|
+
ja: タイトル指定でメモファイルを削除する
|
|
13
13
|
runtime: python
|
|
14
14
|
output_mode: raw
|
|
15
15
|
side_effect: true
|
|
@@ -35,23 +35,12 @@ input:
|
|
|
35
35
|
type: object
|
|
36
36
|
additionalProperties: false
|
|
37
37
|
properties:
|
|
38
|
-
|
|
39
|
-
type: string
|
|
40
|
-
description: "対象日 (YYYY-MM-DD)。省略時は今日"
|
|
41
|
-
description_i18n:
|
|
42
|
-
en: "Target date (YYYY-MM-DD). Defaults to today"
|
|
43
|
-
ja: "対象日 (YYYY-MM-DD)。省略時は今日"
|
|
44
|
-
match:
|
|
38
|
+
title:
|
|
45
39
|
type: string
|
|
46
40
|
minLength: 1
|
|
47
|
-
description:
|
|
48
|
-
description_i18n:
|
|
49
|
-
en: Text contained in the memo line to identify
|
|
50
|
-
ja: メモ行に含まれる文字列で特定する
|
|
51
|
-
index:
|
|
52
|
-
type: integer
|
|
53
|
-
minimum: 1
|
|
54
|
-
description: 該当日メモ行の1始まり番号で特定する
|
|
41
|
+
description: 削除するメモのタイトル(完全一致または部分一致)
|
|
55
42
|
description_i18n:
|
|
56
|
-
en:
|
|
57
|
-
ja:
|
|
43
|
+
en: Title of the memo to delete (exact or partial match)
|
|
44
|
+
ja: 削除するメモのタイトル(完全一致または部分一致)
|
|
45
|
+
required:
|
|
46
|
+
- title
|
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
"""Builtin: memo-index
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
Chroma の collection (default: memo) に upsert する。
|
|
3
|
+
notes/memo/*.md を1ファイル=1ドキュメントとして Chroma collection に upsert する。
|
|
5
4
|
|
|
6
5
|
入力:
|
|
7
|
-
args.since: ISO8601 文字列 (この時刻より古いメモはスキップ、任意)
|
|
8
6
|
args.collection: 索引コレクション名 (default: memo)
|
|
9
7
|
|
|
10
8
|
出力:
|
|
11
|
-
data: {added, skipped, total, collection}
|
|
9
|
+
data: {added, updated, skipped, total, collection}
|
|
12
10
|
"""
|
|
13
11
|
|
|
14
12
|
from __future__ import annotations
|
|
15
13
|
|
|
16
14
|
import hashlib
|
|
17
15
|
import os
|
|
16
|
+
import re
|
|
18
17
|
import sys
|
|
19
|
-
from datetime import datetime
|
|
20
18
|
from pathlib import Path
|
|
21
19
|
|
|
22
20
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_shared"))
|
|
@@ -24,12 +22,13 @@ from i18n import localizer # noqa: E402
|
|
|
24
22
|
|
|
25
23
|
|
|
26
24
|
EMBED_MODEL = "paraphrase-multilingual-MiniLM-L12-v2"
|
|
25
|
+
FRONTMATTER_RE = re.compile(r"^---\n(.*?)\n---\n?", re.DOTALL)
|
|
26
|
+
MAX_DOC_CHARS = 8000
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
async def run(ctx, input):
|
|
30
30
|
loc = localizer(input)
|
|
31
31
|
args = input.get("args", {})
|
|
32
|
-
since_str = args.get("since")
|
|
33
32
|
collection_name = args.get("collection", "memo")
|
|
34
33
|
sources = input.get("sources", {})
|
|
35
34
|
notes_dir = sources.get("notes_dir")
|
|
@@ -59,13 +58,13 @@ async def run(ctx, input):
|
|
|
59
58
|
chroma_path = Path(index_dir).expanduser().resolve() / "local-index" / "chroma"
|
|
60
59
|
chroma_path.mkdir(parents=True, exist_ok=True)
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
if not
|
|
64
|
-
ctx.log.info("memo-index:
|
|
61
|
+
memo_dir = Path(notes_dir).expanduser().resolve() / "memo"
|
|
62
|
+
if not memo_dir.exists():
|
|
63
|
+
ctx.log.info("memo-index: memo dir does not exist yet")
|
|
65
64
|
return result_ok(
|
|
66
65
|
loc.t("Nothing to index", "索引化対象なし"),
|
|
67
66
|
loc.t("There are no memos yet.", "まだメモがありません"),
|
|
68
|
-
{"added": 0, "skipped": 0, "total": 0, "collection": collection_name},
|
|
67
|
+
{"added": 0, "updated": 0, "skipped": 0, "total": 0, "collection": collection_name},
|
|
69
68
|
)
|
|
70
69
|
|
|
71
70
|
try:
|
|
@@ -80,66 +79,73 @@ async def run(ctx, input):
|
|
|
80
79
|
client = chromadb.PersistentClient(path=str(chroma_path))
|
|
81
80
|
col = client.get_or_create_collection(name=collection_name, embedding_function=ef)
|
|
82
81
|
|
|
82
|
+
existing = {}
|
|
83
83
|
try:
|
|
84
|
-
|
|
84
|
+
snapshot = col.get()
|
|
85
|
+
for doc_id, meta in zip(snapshot.get("ids", []), snapshot.get("metadatas", [])):
|
|
86
|
+
existing[doc_id] = meta or {}
|
|
85
87
|
except Exception:
|
|
86
|
-
|
|
88
|
+
existing = {}
|
|
87
89
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
notes_root = Path(notes_dir).expanduser().resolve()
|
|
91
|
+
add_docs, add_ids, add_metas = [], [], []
|
|
92
|
+
upd_docs, upd_ids, upd_metas = [], [], []
|
|
93
|
+
total = added = updated = skipped = 0
|
|
91
94
|
|
|
92
|
-
for md in sorted(
|
|
95
|
+
for md in sorted(memo_dir.glob("*.md")):
|
|
93
96
|
try:
|
|
94
|
-
|
|
95
|
-
except
|
|
97
|
+
raw = md.read_text(encoding="utf-8", errors="ignore")
|
|
98
|
+
except OSError:
|
|
96
99
|
continue
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
total += 1
|
|
101
|
+
fm, body = split_frontmatter(raw)
|
|
102
|
+
body_clean = body.strip()
|
|
103
|
+
if not body_clean:
|
|
104
|
+
skipped += 1
|
|
99
105
|
continue
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
if len(body_clean) > MAX_DOC_CHARS:
|
|
107
|
+
body_clean = body_clean[:MAX_DOC_CHARS]
|
|
108
|
+
|
|
109
|
+
file_rel = str(md.relative_to(notes_root))
|
|
110
|
+
doc_id = sha1(file_rel)
|
|
111
|
+
meta = {
|
|
112
|
+
"file": file_rel,
|
|
113
|
+
"title": md.stem,
|
|
114
|
+
"tags": ", ".join(_as_str_list(fm.get("tags"))),
|
|
115
|
+
"created": _as_str(fm.get("created")),
|
|
116
|
+
"updated": _as_str(fm.get("updated")) or _as_str(fm.get("created")),
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
prev = existing.get(doc_id)
|
|
120
|
+
if prev and prev.get("updated") == meta["updated"]:
|
|
121
|
+
skipped += 1
|
|
103
122
|
continue
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
)
|
|
123
|
+
if prev:
|
|
124
|
+
upd_docs.append(body_clean)
|
|
125
|
+
upd_ids.append(doc_id)
|
|
126
|
+
upd_metas.append(meta)
|
|
127
|
+
updated += 1
|
|
128
|
+
else:
|
|
129
|
+
add_docs.append(body_clean)
|
|
130
|
+
add_ids.append(doc_id)
|
|
131
|
+
add_metas.append(meta)
|
|
132
132
|
added += 1
|
|
133
133
|
|
|
134
|
-
if
|
|
135
|
-
ctx.log.info(f"memo-index: adding {len(
|
|
136
|
-
col.add(documents=
|
|
134
|
+
if add_docs:
|
|
135
|
+
ctx.log.info(f"memo-index: adding {len(add_docs)} new docs to {collection_name}")
|
|
136
|
+
col.add(documents=add_docs, ids=add_ids, metadatas=add_metas)
|
|
137
|
+
if upd_docs:
|
|
138
|
+
ctx.log.info(f"memo-index: updating {len(upd_docs)} docs in {collection_name}")
|
|
139
|
+
col.update(documents=upd_docs, ids=upd_ids, metadatas=upd_metas)
|
|
137
140
|
|
|
138
|
-
summary = loc.t(
|
|
141
|
+
summary = loc.t(
|
|
142
|
+
f"Added {added} / updated {updated} / skipped {skipped} / found {total}",
|
|
143
|
+
f"追加 {added} / 更新 {updated} / スキップ {skipped} / 検出 {total}",
|
|
144
|
+
)
|
|
139
145
|
return result_ok(
|
|
140
|
-
loc.t(f"Indexed {added} entries", f"{added}件を索引化しました"),
|
|
146
|
+
loc.t(f"Indexed {added + updated} entries", f"{added + updated}件を索引化しました"),
|
|
141
147
|
summary,
|
|
142
|
-
{"added": added, "skipped": skipped, "total": total, "collection": collection_name},
|
|
148
|
+
{"added": added, "updated": updated, "skipped": skipped, "total": total, "collection": collection_name},
|
|
143
149
|
)
|
|
144
150
|
|
|
145
151
|
|
|
@@ -147,13 +153,39 @@ def sha1(value):
|
|
|
147
153
|
return hashlib.sha1(value.encode("utf-8")).hexdigest()
|
|
148
154
|
|
|
149
155
|
|
|
150
|
-
def
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
def split_frontmatter(raw: str):
|
|
157
|
+
match = FRONTMATTER_RE.match(raw)
|
|
158
|
+
if not match:
|
|
159
|
+
return {}, raw
|
|
160
|
+
fm = {}
|
|
161
|
+
for line in match.group(1).splitlines():
|
|
162
|
+
if not line.strip() or ":" not in line:
|
|
163
|
+
continue
|
|
164
|
+
key, _, val = line.partition(":")
|
|
165
|
+
v = val.strip()
|
|
166
|
+
if v.startswith("[") and v.endswith("]"):
|
|
167
|
+
inner = v[1:-1]
|
|
168
|
+
items = [item.strip().strip("\"'") for item in inner.split(",") if item.strip()]
|
|
169
|
+
fm[key.strip()] = items
|
|
170
|
+
else:
|
|
171
|
+
if (v.startswith("\"") and v.endswith("\"")) or (v.startswith("'") and v.endswith("'")):
|
|
172
|
+
v = v[1:-1]
|
|
173
|
+
fm[key.strip()] = v
|
|
174
|
+
return fm, raw[match.end():]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _as_str(value):
|
|
178
|
+
if value is None:
|
|
179
|
+
return ""
|
|
180
|
+
return str(value)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _as_str_list(value):
|
|
184
|
+
if value is None:
|
|
185
|
+
return []
|
|
186
|
+
if isinstance(value, list):
|
|
187
|
+
return [str(v) for v in value]
|
|
188
|
+
return [str(value)]
|
|
157
189
|
|
|
158
190
|
|
|
159
191
|
def result_ok(title, summary, data):
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Builtin: memo-index
|
|
2
|
-
#
|
|
2
|
+
# notes/memo/*.md を1ファイル=1ドキュメントとして Chroma ベクター DB に索引化する。
|
|
3
3
|
# 索引は ~/.agent-sin/index/local-index/chroma に永続化、collection は memo (デフォルト)。
|
|
4
4
|
# 多言語対応のため SentenceTransformer (paraphrase-multilingual-MiniLM-L12-v2) を使う。
|
|
5
5
|
# Python 依存: chromadb, sentence-transformers
|
|
@@ -12,10 +12,10 @@ name: Memo Index
|
|
|
12
12
|
name_i18n:
|
|
13
13
|
en: Memo Index
|
|
14
14
|
ja: メモ索引化
|
|
15
|
-
description:
|
|
15
|
+
description: notes/memo配下のメモをChromaベクターDBに索引化する
|
|
16
16
|
description_i18n:
|
|
17
|
-
en: Index
|
|
18
|
-
ja:
|
|
17
|
+
en: Index notes/memo files into a Chroma vector database
|
|
18
|
+
ja: notes/memo配下のメモをChromaベクターDBに索引化する
|
|
19
19
|
runtime: python
|
|
20
20
|
|
|
21
21
|
invocation:
|
|
@@ -36,12 +36,6 @@ input:
|
|
|
36
36
|
type: object
|
|
37
37
|
additionalProperties: false
|
|
38
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
39
|
collection:
|
|
46
40
|
type: string
|
|
47
41
|
default: memo
|