bilinc 2.6.2__tar.gz → 2.7.0__tar.gz
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.
- {bilinc-2.6.2/src/bilinc.egg-info → bilinc-2.7.0}/PKG-INFO +7 -5
- {bilinc-2.6.2 → bilinc-2.7.0}/README.md +6 -4
- {bilinc-2.6.2 → bilinc-2.7.0}/pyproject.toml +2 -2
- {bilinc-2.6.2 → bilinc-2.7.0}/src/bilinc/__init__.py +2 -2
- {bilinc-2.6.2 → bilinc-2.7.0}/src/bilinc/cli/main.py +243 -0
- {bilinc-2.6.2 → bilinc-2.7.0}/src/bilinc/client.py +223 -10
- {bilinc-2.6.2 → bilinc-2.7.0}/src/bilinc/cloud_mcp.py +156 -3
- bilinc-2.7.0/src/bilinc/wiki/__init__.py +6 -0
- bilinc-2.7.0/src/bilinc/wiki/export.py +226 -0
- bilinc-2.7.0/src/bilinc/wiki/fetch.py +240 -0
- bilinc-2.7.0/src/bilinc/wiki/links.py +53 -0
- bilinc-2.7.0/src/bilinc/wiki/location.py +285 -0
- bilinc-2.7.0/src/bilinc/wiki/manifest.py +172 -0
- bilinc-2.7.0/src/bilinc/wiki/paths.py +200 -0
- bilinc-2.7.0/src/bilinc/wiki/render.py +431 -0
- bilinc-2.7.0/src/bilinc/wiki/reuse.py +157 -0
- bilinc-2.7.0/src/bilinc/wiki/writer.py +344 -0
- {bilinc-2.6.2 → bilinc-2.7.0/src/bilinc.egg-info}/PKG-INFO +7 -5
- {bilinc-2.6.2 → bilinc-2.7.0}/src/bilinc.egg-info/SOURCES.txt +10 -0
- {bilinc-2.6.2 → bilinc-2.7.0}/tests/test_cloud_only_package.py +13 -6
- {bilinc-2.6.2 → bilinc-2.7.0}/LICENSE +0 -0
- {bilinc-2.6.2 → bilinc-2.7.0}/MANIFEST.in +0 -0
- {bilinc-2.6.2 → bilinc-2.7.0}/setup.cfg +0 -0
- {bilinc-2.6.2 → bilinc-2.7.0}/setup.py +0 -0
- {bilinc-2.6.2 → bilinc-2.7.0}/src/bilinc/cli/__init__.py +0 -0
- {bilinc-2.6.2 → bilinc-2.7.0}/src/bilinc/cli/__main__.py +0 -0
- {bilinc-2.6.2 → bilinc-2.7.0}/src/bilinc/cli/login.py +0 -0
- {bilinc-2.6.2 → bilinc-2.7.0}/src/bilinc.egg-info/dependency_links.txt +0 -0
- {bilinc-2.6.2 → bilinc-2.7.0}/src/bilinc.egg-info/entry_points.txt +0 -0
- {bilinc-2.6.2 → bilinc-2.7.0}/src/bilinc.egg-info/requires.txt +0 -0
- {bilinc-2.6.2 → bilinc-2.7.0}/src/bilinc.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bilinc
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7.0
|
|
4
4
|
Summary: Cloud-only memory SDK and MCP adapter for autonomous agents
|
|
5
5
|
Author: Bilinc Team
|
|
6
6
|
License-Expression: BUSL-1.1
|
|
@@ -185,6 +185,9 @@ The config carries no key; the adapter reads the one `bilinc login` saved. On a
|
|
|
185
185
|
| `list_memories` | Browse what is stored. Free. |
|
|
186
186
|
| `history` | One memory's changes, newest first. Free. |
|
|
187
187
|
| `confirm` | Record that a memory is still accurate. One write. |
|
|
188
|
+
| `dream_start` | Start a dream pass: the entries changed since the last one, for your agent to review. Stdio adapter; coming to the hosted connector. |
|
|
189
|
+
| `dream_apply` | Apply the dream's merges, retirements, confirmations and flags. **Destructive**; undo with `dream_undo`. Stdio adapter; coming to the hosted connector. |
|
|
190
|
+
| `dream_undo` | Undo a dream in one step, for 7 days. Stdio adapter; coming to the hosted connector. |
|
|
188
191
|
|
|
189
192
|
The remote connector at `https://mcp.bilinc.space/mcp` has ten tools; a read-only connection lists only the four read tools. Client guides: [Claude Code](https://bilinc.space/docs/claude-code) · [Codex](https://bilinc.space/docs/codex) · [Cursor](https://bilinc.space/docs/cursor) · [any MCP client](https://bilinc.space/for/mcp).
|
|
190
193
|
|
|
@@ -211,11 +214,10 @@ All endpoints live under `https://bilinc.space`. Memory operations need an API k
|
|
|
211
214
|
|
|
212
215
|
Send an `Idempotency-Key` header on any write you might retry: the same key with the same payload replays the original result and is billed once; a different payload is refused with `409 idempotency_conflict`. Saves that contain a credential, such as an API key, token or private key, are refused with `secret_detected` and the value is never echoed back.
|
|
213
216
|
|
|
214
|
-
## What's new in 2.
|
|
217
|
+
## What's new in 2.7.0
|
|
215
218
|
|
|
216
|
-
- **
|
|
217
|
-
- **
|
|
218
|
-
- **Nothing else moves**: queries without a time phrase are answered exactly as before.
|
|
219
|
+
- **Wiki export**: `bilinc wiki <dir>` writes your memory as Obsidian-compatible Markdown, one page per key, with recent changes, links between keys and an index. It is one-way, never touches files it did not write, and reruns reuse unchanged pages. `--wikilinks` writes `[[...]]` links; the stdio MCP adapter has the same export as `export_wiki`.
|
|
220
|
+
- **Dream pass**: your agent tidies its own memory (merges duplicates, retires stale entries, flags what a person should decide) and you can undo the pass's own changes in one step for 7 days. Bilinc runs no model for this. Available in the SDK, the CLI (`bilinc dream`) and the stdio MCP adapter; coming to the hosted connector.
|
|
219
221
|
|
|
220
222
|
Every release: [changelog](https://bilinc.space/changelog).
|
|
221
223
|
|
|
@@ -137,6 +137,9 @@ The config carries no key; the adapter reads the one `bilinc login` saved. On a
|
|
|
137
137
|
| `list_memories` | Browse what is stored. Free. |
|
|
138
138
|
| `history` | One memory's changes, newest first. Free. |
|
|
139
139
|
| `confirm` | Record that a memory is still accurate. One write. |
|
|
140
|
+
| `dream_start` | Start a dream pass: the entries changed since the last one, for your agent to review. Stdio adapter; coming to the hosted connector. |
|
|
141
|
+
| `dream_apply` | Apply the dream's merges, retirements, confirmations and flags. **Destructive**; undo with `dream_undo`. Stdio adapter; coming to the hosted connector. |
|
|
142
|
+
| `dream_undo` | Undo a dream in one step, for 7 days. Stdio adapter; coming to the hosted connector. |
|
|
140
143
|
|
|
141
144
|
The remote connector at `https://mcp.bilinc.space/mcp` has ten tools; a read-only connection lists only the four read tools. Client guides: [Claude Code](https://bilinc.space/docs/claude-code) · [Codex](https://bilinc.space/docs/codex) · [Cursor](https://bilinc.space/docs/cursor) · [any MCP client](https://bilinc.space/for/mcp).
|
|
142
145
|
|
|
@@ -163,11 +166,10 @@ All endpoints live under `https://bilinc.space`. Memory operations need an API k
|
|
|
163
166
|
|
|
164
167
|
Send an `Idempotency-Key` header on any write you might retry: the same key with the same payload replays the original result and is billed once; a different payload is refused with `409 idempotency_conflict`. Saves that contain a credential, such as an API key, token or private key, are refused with `secret_detected` and the value is never echoed back.
|
|
165
168
|
|
|
166
|
-
## What's new in 2.
|
|
169
|
+
## What's new in 2.7.0
|
|
167
170
|
|
|
168
|
-
- **
|
|
169
|
-
- **
|
|
170
|
-
- **Nothing else moves**: queries without a time phrase are answered exactly as before.
|
|
171
|
+
- **Wiki export**: `bilinc wiki <dir>` writes your memory as Obsidian-compatible Markdown, one page per key, with recent changes, links between keys and an index. It is one-way, never touches files it did not write, and reruns reuse unchanged pages. `--wikilinks` writes `[[...]]` links; the stdio MCP adapter has the same export as `export_wiki`.
|
|
172
|
+
- **Dream pass**: your agent tidies its own memory (merges duplicates, retires stale entries, flags what a person should decide) and you can undo the pass's own changes in one step for 7 days. Bilinc runs no model for this. Available in the SDK, the CLI (`bilinc dream`) and the stdio MCP adapter; coming to the hosted connector.
|
|
171
173
|
|
|
172
174
|
Every release: [changelog](https://bilinc.space/changelog).
|
|
173
175
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "bilinc"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.7.0"
|
|
8
8
|
description = "Cloud-only memory SDK and MCP adapter for autonomous agents"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "BUSL-1.1"
|
|
@@ -75,7 +75,7 @@ bilinc = "bilinc.cli.main:main"
|
|
|
75
75
|
|
|
76
76
|
[tool.setuptools]
|
|
77
77
|
package-dir = {"" = "src"}
|
|
78
|
-
packages = ["bilinc", "bilinc.cli"]
|
|
78
|
+
packages = ["bilinc", "bilinc.cli", "bilinc.wiki"]
|
|
79
79
|
|
|
80
80
|
[tool.pytest.ini_options]
|
|
81
81
|
testpaths = ["tests"]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Bilinc 2.
|
|
1
|
+
"""Bilinc 2.7.0: cloud-only SDK for agent memory."""
|
|
2
2
|
|
|
3
3
|
from bilinc.client import (
|
|
4
4
|
ACTIVATION_SIGNUP_URL,
|
|
@@ -25,7 +25,7 @@ from bilinc.client import (
|
|
|
25
25
|
CloudClient,
|
|
26
26
|
)
|
|
27
27
|
|
|
28
|
-
__version__ = "2.
|
|
28
|
+
__version__ = "2.7.0"
|
|
29
29
|
version = __version__
|
|
30
30
|
|
|
31
31
|
__all__ = [
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
|
|
6
6
|
import argparse
|
|
7
7
|
import contextlib
|
|
8
|
+
import http.client
|
|
8
9
|
import json
|
|
9
10
|
import os
|
|
10
11
|
import shlex
|
|
@@ -24,6 +25,7 @@ from bilinc.client import (
|
|
|
24
25
|
BilincValidationError,
|
|
25
26
|
CloudClient,
|
|
26
27
|
INSTALL_URL,
|
|
28
|
+
MAX_HISTORY_LIMIT,
|
|
27
29
|
MEMORY_TYPES,
|
|
28
30
|
SIGNUP_URL,
|
|
29
31
|
VALUE_MODES,
|
|
@@ -32,6 +34,7 @@ from bilinc.client import (
|
|
|
32
34
|
save_config_api_key,
|
|
33
35
|
)
|
|
34
36
|
from bilinc.cli.login import LoginError, device_login, is_headless, loopback_login
|
|
37
|
+
from bilinc.wiki import WikiExportError, export_wiki, summary_line
|
|
35
38
|
|
|
36
39
|
|
|
37
40
|
def _parse_value(value: str) -> Any:
|
|
@@ -169,6 +172,194 @@ def _write_private_json(path: str, payload: dict[str, Any]) -> None:
|
|
|
169
172
|
raise
|
|
170
173
|
|
|
171
174
|
|
|
175
|
+
def _history_count(text: str) -> int:
|
|
176
|
+
try:
|
|
177
|
+
value = int(text)
|
|
178
|
+
except ValueError:
|
|
179
|
+
raise argparse.ArgumentTypeError(f"must be a whole number from 0 to {MAX_HISTORY_LIMIT}") from None
|
|
180
|
+
if not 0 <= value <= MAX_HISTORY_LIMIT:
|
|
181
|
+
raise argparse.ArgumentTypeError(f"must be a whole number from 0 to {MAX_HISTORY_LIMIT}")
|
|
182
|
+
return value
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _name_some(keys: list[str], limit: int = 20) -> str:
|
|
186
|
+
shown = ", ".join(keys[:limit])
|
|
187
|
+
return shown + (f", … ({len(keys) - limit} more)" if len(keys) > limit else "")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _read_dream_actions(
|
|
191
|
+
parser: argparse.ArgumentParser, source: str
|
|
192
|
+
) -> tuple[list[dict[str, Any]], dict[str, Any] | None]:
|
|
193
|
+
"""Load `{"actions": [...], "report": {...}}` or a bare action list; bad input is a usage error."""
|
|
194
|
+
try:
|
|
195
|
+
if source == "-":
|
|
196
|
+
text = sys.stdin.read()
|
|
197
|
+
else:
|
|
198
|
+
with open(source, encoding="utf-8") as handle:
|
|
199
|
+
text = handle.read()
|
|
200
|
+
except OSError as exc:
|
|
201
|
+
parser.error(f"dream apply: cannot read {source}: {exc.strerror or exc}")
|
|
202
|
+
try:
|
|
203
|
+
payload = json.loads(text)
|
|
204
|
+
except json.JSONDecodeError as exc:
|
|
205
|
+
parser.error(f"dream apply: {source} is not valid JSON ({exc.msg}, line {exc.lineno})")
|
|
206
|
+
report = None
|
|
207
|
+
if isinstance(payload, dict):
|
|
208
|
+
actions = payload.get("actions")
|
|
209
|
+
report = payload.get("report")
|
|
210
|
+
if report is not None and not isinstance(report, dict):
|
|
211
|
+
parser.error("dream apply: report must be a JSON object")
|
|
212
|
+
else:
|
|
213
|
+
actions = payload
|
|
214
|
+
if not isinstance(actions, list):
|
|
215
|
+
parser.error('dream apply: expected {"actions": [...]} or a JSON list of actions')
|
|
216
|
+
return actions, report
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _dream_parser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
|
|
220
|
+
commands = next(action for action in parser._actions if isinstance(action, argparse._SubParsersAction))
|
|
221
|
+
return commands.choices["dream"]
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _print_dream_start(result: dict[str, Any]) -> None:
|
|
225
|
+
if not result.get("dream_id"):
|
|
226
|
+
print(f"Nothing changed since {result.get('since') or 'the last dream'}; no dream started.")
|
|
227
|
+
return
|
|
228
|
+
candidates = result.get("candidates") or []
|
|
229
|
+
count = result.get("candidate_count", len(candidates))
|
|
230
|
+
window = result.get("undo_window_days", 7)
|
|
231
|
+
print(
|
|
232
|
+
f"dream {result['dream_id']}: {count} candidates, forget cap {result.get('forget_cap', 0)}, "
|
|
233
|
+
f"undo window {window} days"
|
|
234
|
+
)
|
|
235
|
+
for item in candidates:
|
|
236
|
+
print(
|
|
237
|
+
f"{item.get('key')} [{item.get('memory_type') or '-'}] "
|
|
238
|
+
f"{_short_time(item.get('updated_at'))} {item.get('entry_version') or '-'}"
|
|
239
|
+
)
|
|
240
|
+
if result.get("truncated"):
|
|
241
|
+
print("(more candidates than shown; run again after this dream, or narrow with --prefix)")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _print_dream_results(results: list[dict[str, Any]]) -> None:
|
|
245
|
+
totals = {"applied": 0, "conflict": 0, "error": 0, "skipped": 0}
|
|
246
|
+
for row in results:
|
|
247
|
+
outcome = str(row.get("result") or "-")
|
|
248
|
+
if outcome in totals:
|
|
249
|
+
totals[outcome] += 1
|
|
250
|
+
line = f"{row.get('n')} {row.get('op')} {row.get('key')} {outcome}"
|
|
251
|
+
if row.get("error"):
|
|
252
|
+
line += f" {row['error']}"
|
|
253
|
+
print(line)
|
|
254
|
+
print(
|
|
255
|
+
f"applied {totals['applied']}, conflicts {totals['conflict']}, "
|
|
256
|
+
f"errors {totals['error']}, skipped {totals['skipped']}"
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _print_dream_undo(result: dict[str, Any]) -> None:
|
|
261
|
+
print(f"dream {result.get('dream_id')}: {result.get('status')}")
|
|
262
|
+
restored = result.get("restored") or []
|
|
263
|
+
conflicts = result.get("conflicts") or []
|
|
264
|
+
kept = result.get("kept") or []
|
|
265
|
+
print(f"restored ({len(restored)})")
|
|
266
|
+
for row in restored:
|
|
267
|
+
line = f" {row.get('n')} {row.get('op')} {row.get('key')}"
|
|
268
|
+
if row.get("metadata_restored") is False:
|
|
269
|
+
line += " (metadata not restored)"
|
|
270
|
+
print(line)
|
|
271
|
+
print(f"conflicts ({len(conflicts)})")
|
|
272
|
+
for row in conflicts:
|
|
273
|
+
print(f" {row.get('n')} {row.get('key')} {row.get('reason') or ''}".rstrip())
|
|
274
|
+
print(f"kept ({len(kept)})")
|
|
275
|
+
for row in kept:
|
|
276
|
+
print(f" {row.get('n')} {row.get('key')}")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _run_dream(client: CloudClient, args: argparse.Namespace, parser: argparse.ArgumentParser) -> int:
|
|
280
|
+
if args.dream_command == "start":
|
|
281
|
+
result = client.dream_start(prefix=args.prefix, since=args.since, limit=args.limit)
|
|
282
|
+
if args.json:
|
|
283
|
+
_print(result)
|
|
284
|
+
else:
|
|
285
|
+
_print_dream_start(result)
|
|
286
|
+
elif args.dream_command == "apply":
|
|
287
|
+
actions, report = _read_dream_actions(parser, args.file)
|
|
288
|
+
try:
|
|
289
|
+
result = client.dream_apply(args.dream_id, actions, report=report)
|
|
290
|
+
except BilincCloudError as exc:
|
|
291
|
+
partial = getattr(exc, "partial", None)
|
|
292
|
+
if isinstance(partial, dict) and partial.get("results"):
|
|
293
|
+
_print_dream_results(partial["results"])
|
|
294
|
+
raise
|
|
295
|
+
_print_dream_results(result.get("results") or [])
|
|
296
|
+
elif args.dream_command == "undo":
|
|
297
|
+
result = client.dream_undo(args.dream_id)
|
|
298
|
+
if args.json:
|
|
299
|
+
_print(result)
|
|
300
|
+
else:
|
|
301
|
+
_print_dream_undo(result)
|
|
302
|
+
elif args.dream_command == "show":
|
|
303
|
+
_print(client.dream_show(args.dream_id))
|
|
304
|
+
return 0
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _run_wiki(client: CloudClient, args: argparse.Namespace) -> int:
|
|
308
|
+
"""Export to a folder; one summary line on stdout, everything else on stderr."""
|
|
309
|
+
counted = []
|
|
310
|
+
|
|
311
|
+
def progress(done: int, total: int) -> None:
|
|
312
|
+
counted.append(done)
|
|
313
|
+
print(f"\rhistory {done}/{total}", end="", file=sys.stderr, flush=True)
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
result = export_wiki(
|
|
317
|
+
client,
|
|
318
|
+
args.dir,
|
|
319
|
+
history=args.history,
|
|
320
|
+
prefix=args.prefix,
|
|
321
|
+
wikilinks=args.wikilinks,
|
|
322
|
+
refresh_history=args.refresh_history,
|
|
323
|
+
progress=progress if sys.stderr.isatty() else None,
|
|
324
|
+
)
|
|
325
|
+
except KeyboardInterrupt:
|
|
326
|
+
if counted:
|
|
327
|
+
print(file=sys.stderr)
|
|
328
|
+
print("bilinc: wiki export cancelled", file=sys.stderr)
|
|
329
|
+
return 130
|
|
330
|
+
except WikiExportError as exc:
|
|
331
|
+
if counted:
|
|
332
|
+
print(file=sys.stderr)
|
|
333
|
+
print(f"bilinc: error: {exc}", file=sys.stderr)
|
|
334
|
+
return 1
|
|
335
|
+
except (OSError, http.client.HTTPException) as exc:
|
|
336
|
+
# The SDK's transport lets a timeout or dropped connection while reading
|
|
337
|
+
# a response through unwrapped; nothing was written at that point.
|
|
338
|
+
if counted:
|
|
339
|
+
print(file=sys.stderr)
|
|
340
|
+
print(f"bilinc: error: Bilinc Cloud request failed: {exc}", file=sys.stderr)
|
|
341
|
+
return 1
|
|
342
|
+
if counted:
|
|
343
|
+
print(file=sys.stderr)
|
|
344
|
+
for line in result.warnings:
|
|
345
|
+
print(line, file=sys.stderr)
|
|
346
|
+
if result.history_unavailable:
|
|
347
|
+
count = len(result.history_unavailable)
|
|
348
|
+
print(
|
|
349
|
+
f"warning: history unavailable for {count} key{'s' if count != 1 else ''}: "
|
|
350
|
+
f"{_name_some(result.history_unavailable)}",
|
|
351
|
+
file=sys.stderr,
|
|
352
|
+
)
|
|
353
|
+
if result.skipped:
|
|
354
|
+
print(
|
|
355
|
+
"warning: left these previously exported files alone because a folder on the way "
|
|
356
|
+
f"is no longer a real folder: {_name_some(result.skipped)}",
|
|
357
|
+
file=sys.stderr,
|
|
358
|
+
)
|
|
359
|
+
print(summary_line(result, args.dir))
|
|
360
|
+
return 0
|
|
361
|
+
|
|
362
|
+
|
|
172
363
|
def _client(args: argparse.Namespace) -> CloudClient:
|
|
173
364
|
return CloudClient(api_key=args.api_key, base_url=args.base_url, timeout=args.timeout)
|
|
174
365
|
|
|
@@ -335,6 +526,51 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
335
526
|
export.add_argument("--prefix", help="Only keys starting with this text")
|
|
336
527
|
export.add_argument("--type", choices=MEMORY_TYPES, help="Only this memory type")
|
|
337
528
|
|
|
529
|
+
wiki_help = "Write every stored memory as Obsidian-compatible Markdown pages in a folder"
|
|
530
|
+
wiki = sub.add_parser("wiki", help=wiki_help, description=wiki_help)
|
|
531
|
+
wiki.add_argument("dir", help="Folder to write: new, empty, or a previous bilinc wiki export")
|
|
532
|
+
wiki.add_argument(
|
|
533
|
+
"--history",
|
|
534
|
+
type=_history_count,
|
|
535
|
+
default=5,
|
|
536
|
+
metavar="N",
|
|
537
|
+
help=f"Recorded changes per page, newest first (0-{MAX_HISTORY_LIMIT}, default 5; 0 skips history)",
|
|
538
|
+
)
|
|
539
|
+
wiki.add_argument("--prefix", help="Only keys starting with this text")
|
|
540
|
+
wiki.add_argument(
|
|
541
|
+
"--wikilinks",
|
|
542
|
+
action="store_true",
|
|
543
|
+
help="Write [[...]] links (Obsidian style) instead of standard Markdown links",
|
|
544
|
+
)
|
|
545
|
+
wiki.add_argument(
|
|
546
|
+
"--refresh-history",
|
|
547
|
+
action="store_true",
|
|
548
|
+
help="Fetch every page's history again; by default history is reused only when "
|
|
549
|
+
"nothing changed since the last export",
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
dream_help = "Tidy your agent's memory in a dream pass, and undo it in one step"
|
|
553
|
+
dream = sub.add_parser("dream", help=dream_help, description=dream_help)
|
|
554
|
+
dream_sub = dream.add_subparsers(dest="dream_command")
|
|
555
|
+
dream_start = dream_sub.add_parser("start", help="Start a dream and list the candidate entries")
|
|
556
|
+
dream_start.add_argument("--prefix", help="Only keys starting with this text")
|
|
557
|
+
dream_start.add_argument("--since", help="Only entries changed after this ISO-8601 time")
|
|
558
|
+
dream_start.add_argument("--limit", type=int, default=200, help="Most candidates to return (1-200)")
|
|
559
|
+
dream_start.add_argument("--json", action="store_true", help="Print the raw JSON response")
|
|
560
|
+
dream_apply = dream_sub.add_parser("apply", help="Apply a dream's actions from a JSON file or stdin")
|
|
561
|
+
dream_apply.add_argument("dream_id", help="The id printed by bilinc dream start")
|
|
562
|
+
dream_apply.add_argument(
|
|
563
|
+
"--file",
|
|
564
|
+
default="-",
|
|
565
|
+
help='JSON with {"actions": [...], "report": {...}} or a bare action list; - reads stdin (default)',
|
|
566
|
+
)
|
|
567
|
+
dream_undo = dream_sub.add_parser("undo", help="Undo a dream's own changes")
|
|
568
|
+
dream_undo.add_argument("dream_id")
|
|
569
|
+
dream_undo.add_argument("--json", action="store_true", help="Print the raw JSON response")
|
|
570
|
+
dream_show = dream_sub.add_parser("show", help="Show a dream's report")
|
|
571
|
+
dream_show.add_argument("dream_id")
|
|
572
|
+
dream_show.add_argument("--json", action="store_true", help="Print the raw JSON response (the default)")
|
|
573
|
+
|
|
338
574
|
snapshot = sub.add_parser("snapshot", help="Create or list project checkpoints")
|
|
339
575
|
snapshot.add_argument("action", choices=["create", "list"], nargs="?", default="create")
|
|
340
576
|
snapshot.add_argument("--label", help="Human-readable label for a new checkpoint")
|
|
@@ -474,6 +710,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
474
710
|
if args.command is None:
|
|
475
711
|
parser.print_help()
|
|
476
712
|
return 0
|
|
713
|
+
if args.command == "dream" and args.dream_command is None:
|
|
714
|
+
_dream_parser(parser).print_help(sys.stderr)
|
|
715
|
+
return 2
|
|
477
716
|
|
|
478
717
|
try:
|
|
479
718
|
client = _client(args)
|
|
@@ -571,6 +810,10 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
571
810
|
print(f"Exported {exported['count']} memories to {args.output}", file=sys.stderr)
|
|
572
811
|
else:
|
|
573
812
|
_print(exported)
|
|
813
|
+
elif args.command == "wiki":
|
|
814
|
+
return _run_wiki(client, args)
|
|
815
|
+
elif args.command == "dream":
|
|
816
|
+
return _run_dream(client, args, parser)
|
|
574
817
|
elif args.command == "snapshot":
|
|
575
818
|
if args.action == "list":
|
|
576
819
|
_print(client.list_snapshots(limit=args.limit))
|