nmem-cli 0.8.3__tar.gz → 0.8.8__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.
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/PKG-INFO +1 -1
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/pyproject.toml +1 -1
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/__init__.py +1 -1
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/cli.py +15 -0
- nmem_cli-0.8.8/src/nmem_cli/kfs_cli.py +334 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/.gitignore +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/README.md +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/claude_paths.py +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/license_payload.py +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/py.typed +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/session_import.py +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/tui/__init__.py +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/tui/__main__.py +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/tui/api_client.py +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/tui/app.py +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/tui/screens/__init__.py +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/tui/screens/dashboard.py +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/tui/screens/graph.py +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/tui/screens/help.py +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/tui/screens/memories.py +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/tui/screens/memory_detail.py +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/tui/screens/settings.py +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/tui/screens/thread_detail.py +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/tui/screens/threads.py +0 -0
- {nmem_cli-0.8.3 → nmem_cli-0.8.8}/src/nmem_cli/tui/widgets/__init__.py +0 -0
|
@@ -33,6 +33,7 @@ from rich.prompt import Confirm, Prompt
|
|
|
33
33
|
from rich.table import Table
|
|
34
34
|
|
|
35
35
|
from . import __version__
|
|
36
|
+
from .kfs_cli import handle_kfs_command, register_kfs_parser
|
|
36
37
|
from .license_payload import decode_license_email
|
|
37
38
|
from .session_import import SessionImportError, discover_sessions, parse_session
|
|
38
39
|
|
|
@@ -9214,6 +9215,9 @@ PRIORITY
|
|
|
9214
9215
|
# stats
|
|
9215
9216
|
subparsers.add_parser("stats", help="Show statistics")
|
|
9216
9217
|
|
|
9218
|
+
# Knowledge Filesystem
|
|
9219
|
+
register_kfs_parser(subparsers)
|
|
9220
|
+
|
|
9217
9221
|
# export / import
|
|
9218
9222
|
export_parser = subparsers.add_parser("export", help="Export data to folder/zip (experimental)")
|
|
9219
9223
|
export_parser.add_argument("path", help="Target export directory or .zip path")
|
|
@@ -10604,6 +10608,17 @@ def main() -> int:
|
|
|
10604
10608
|
return 1
|
|
10605
10609
|
elif cmd == "stats":
|
|
10606
10610
|
cmd_stats()
|
|
10611
|
+
elif cmd == "fs":
|
|
10612
|
+
return handle_kfs_command(
|
|
10613
|
+
args,
|
|
10614
|
+
api_get=api_get,
|
|
10615
|
+
api_post=api_post,
|
|
10616
|
+
output_json=output_json,
|
|
10617
|
+
is_json_mode=is_json_mode,
|
|
10618
|
+
console=console,
|
|
10619
|
+
print_success=print_success,
|
|
10620
|
+
print_error=print_error,
|
|
10621
|
+
)
|
|
10607
10622
|
elif cmd == "export":
|
|
10608
10623
|
cmd_export_data(
|
|
10609
10624
|
args.path,
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""Nowledge FS commands for ``nmem fs``.
|
|
2
|
+
|
|
3
|
+
The CLI module is intentionally separate from ``cli.py`` because the main
|
|
4
|
+
entrypoint is already oversized. This file owns only parser registration,
|
|
5
|
+
small output shaping, and dispatch to existing ``/fs/*`` endpoints.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import json
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Callable
|
|
15
|
+
|
|
16
|
+
ApiGet = Callable[[str, dict[str, Any] | None], dict[str, Any]]
|
|
17
|
+
ApiPost = Callable[[str, dict[str, Any]], dict[str, Any]]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def register_kfs_parser(subparsers: argparse._SubParsersAction) -> None:
|
|
21
|
+
fs = subparsers.add_parser(
|
|
22
|
+
"fs",
|
|
23
|
+
help="Nowledge FS for agents and scripts",
|
|
24
|
+
description=(
|
|
25
|
+
"Browse Mem through Nowledge FS. Paths are graph projections, "
|
|
26
|
+
"not files on disk: use ls, cat, stat, find, grep, and recall."
|
|
27
|
+
),
|
|
28
|
+
epilog="""examples:
|
|
29
|
+
nmem fs ls /
|
|
30
|
+
nmem fs cat /wiki/entities/PostgreSQL.entity.md
|
|
31
|
+
nmem fs cat /sources/report-abc123.pdf --line 120 --lines 40
|
|
32
|
+
nmem fs find /memories --label decisions --since 2026-01-01
|
|
33
|
+
nmem fs grep "JWT rotation" /memories
|
|
34
|
+
nmem fs grep "JWT rotation" /threads --jsonl
|
|
35
|
+
nmem fs recall "session token strategy" --in /memories
|
|
36
|
+
cat note.md | nmem fs write /memories/by-id/mem-abc.memory.md --stdin""",
|
|
37
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
38
|
+
)
|
|
39
|
+
fs_subs = fs.add_subparsers(dest="fs_action")
|
|
40
|
+
|
|
41
|
+
ls = fs_subs.add_parser("ls", help="List a Nowledge FS directory")
|
|
42
|
+
_add_json_flag(ls)
|
|
43
|
+
ls.add_argument("path", nargs="?", default="/")
|
|
44
|
+
ls.add_argument("-n", "--limit", type=int, default=200)
|
|
45
|
+
ls.add_argument("--cursor", help="Opaque pagination cursor")
|
|
46
|
+
ls.add_argument("-l", "--long", action="store_true", help="Show paths and hints")
|
|
47
|
+
|
|
48
|
+
cat = fs_subs.add_parser("cat", help="Read a Nowledge FS file")
|
|
49
|
+
_add_json_flag(cat)
|
|
50
|
+
cat.add_argument("path")
|
|
51
|
+
cat.add_argument("--line", type=int, help="One-based line to start reading from")
|
|
52
|
+
cat.add_argument("--lines", type=int, help="Number of lines to print")
|
|
53
|
+
cat.add_argument("--fragment", help="Fragment selector, for future format-aware reads")
|
|
54
|
+
cat.add_argument("--raw", action="store_true", help="Request raw source bytes when supported")
|
|
55
|
+
cat.add_argument("--frontmatter", action="store_true", help="Print frontmatter before body")
|
|
56
|
+
|
|
57
|
+
stat = fs_subs.add_parser("stat", help="Show metadata for a Nowledge FS path")
|
|
58
|
+
_add_json_flag(stat)
|
|
59
|
+
stat.add_argument("path")
|
|
60
|
+
|
|
61
|
+
find = fs_subs.add_parser("find", help="Structural search over Nowledge FS paths")
|
|
62
|
+
_add_json_flag(find)
|
|
63
|
+
find.add_argument("path", nargs="?", default="/memories")
|
|
64
|
+
find.add_argument("--type", choices=["memory", "crystal"], help="Filter by file type")
|
|
65
|
+
find.add_argument("--label", help="Filter by label")
|
|
66
|
+
find.add_argument("--since", help="Lower recorded-time bound")
|
|
67
|
+
find.add_argument("--until", help="Upper recorded-time bound")
|
|
68
|
+
find.add_argument("--mentions", help="Filter memories mentioning this entity")
|
|
69
|
+
find.add_argument("-n", "--limit", type=int, default=50)
|
|
70
|
+
find.add_argument("--cursor", help="Opaque pagination cursor")
|
|
71
|
+
find.add_argument("--jsonl", action="store_true", help="Print one JSON path hit per line")
|
|
72
|
+
find.add_argument("--paths", action="store_true", help="Print paths only")
|
|
73
|
+
|
|
74
|
+
grep = fs_subs.add_parser("grep", help="Literal text search")
|
|
75
|
+
_add_json_flag(grep)
|
|
76
|
+
grep.add_argument("query")
|
|
77
|
+
grep.add_argument("path", nargs="?", default="/memories")
|
|
78
|
+
grep.add_argument("-n", "--limit", type=int, default=50)
|
|
79
|
+
grep.add_argument("--cursor", help="Opaque pagination cursor")
|
|
80
|
+
grep.add_argument("--jsonl", action="store_true", help="Print one JSON match per line")
|
|
81
|
+
grep.add_argument("--paths", action="store_true", help="Print unique matched paths only")
|
|
82
|
+
|
|
83
|
+
recall = fs_subs.add_parser("recall", help="Semantic memory search returning Nowledge FS paths")
|
|
84
|
+
_add_json_flag(recall)
|
|
85
|
+
recall.add_argument("query")
|
|
86
|
+
recall.add_argument("-i", "--in", dest="path", default="/memories")
|
|
87
|
+
recall.add_argument("-k", type=int, default=20, help="Number of results")
|
|
88
|
+
recall.add_argument("--jsonl", action="store_true", help="Print one JSON path hit per line")
|
|
89
|
+
recall.add_argument("--paths", action="store_true", help="Print paths only")
|
|
90
|
+
|
|
91
|
+
write = fs_subs.add_parser("write", help="Write a canonical Nowledge FS path")
|
|
92
|
+
_add_json_flag(write)
|
|
93
|
+
write.add_argument("path")
|
|
94
|
+
body = write.add_mutually_exclusive_group(required=True)
|
|
95
|
+
body.add_argument("--body", help="Markdown body to write")
|
|
96
|
+
body.add_argument("--file", type=Path, help="Read markdown body from a local file")
|
|
97
|
+
body.add_argument("--stdin", action="store_true", help="Read markdown body from stdin")
|
|
98
|
+
write.add_argument("--frontmatter-json", help="JSON object merged into frontmatter")
|
|
99
|
+
write.add_argument("--record-version", help="Optional optimistic record version")
|
|
100
|
+
|
|
101
|
+
rm = fs_subs.add_parser("rm", help="Delete a canonical Nowledge FS path")
|
|
102
|
+
_add_json_flag(rm)
|
|
103
|
+
rm.add_argument("path")
|
|
104
|
+
rm.add_argument("-f", "--force", action="store_true", help="Skip confirmation")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _add_json_flag(parser: argparse.ArgumentParser) -> None:
|
|
108
|
+
parser.add_argument(
|
|
109
|
+
"--json",
|
|
110
|
+
action="store_true",
|
|
111
|
+
default=argparse.SUPPRESS,
|
|
112
|
+
help="JSON output",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def handle_kfs_command(
|
|
117
|
+
args: argparse.Namespace,
|
|
118
|
+
*,
|
|
119
|
+
api_get: ApiGet,
|
|
120
|
+
api_post: ApiPost,
|
|
121
|
+
output_json: Callable[[Any], None],
|
|
122
|
+
is_json_mode: Callable[[], bool],
|
|
123
|
+
console: Any,
|
|
124
|
+
print_success: Callable[[str, str | None], None],
|
|
125
|
+
print_error: Callable[[str, str, str | None], None],
|
|
126
|
+
) -> int:
|
|
127
|
+
action = getattr(args, "fs_action", None)
|
|
128
|
+
if action is None:
|
|
129
|
+
console.print("[bold]nmem fs[/bold] verbs: ls, cat, stat, find, grep, recall, write, rm")
|
|
130
|
+
return 0
|
|
131
|
+
|
|
132
|
+
if action == "ls":
|
|
133
|
+
data = api_get("/fs/ls", _params(path=args.path, limit=args.limit, cursor=args.cursor))
|
|
134
|
+
if is_json_mode():
|
|
135
|
+
output_json(data)
|
|
136
|
+
else:
|
|
137
|
+
_print_ls(console, data, long=getattr(args, "long", False))
|
|
138
|
+
return 0
|
|
139
|
+
|
|
140
|
+
if action == "cat":
|
|
141
|
+
data = api_get(
|
|
142
|
+
"/fs/cat",
|
|
143
|
+
_params(
|
|
144
|
+
path=args.path,
|
|
145
|
+
line=getattr(args, "line", None),
|
|
146
|
+
lines=getattr(args, "lines", None),
|
|
147
|
+
fragment=args.fragment,
|
|
148
|
+
raw=args.raw or None,
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
if is_json_mode():
|
|
152
|
+
output_json(data)
|
|
153
|
+
else:
|
|
154
|
+
if getattr(args, "frontmatter", False) and data.get("frontmatter"):
|
|
155
|
+
console.print(json.dumps(data["frontmatter"], indent=2, ensure_ascii=False))
|
|
156
|
+
console.print(str(data.get("body") or ""), end="" if str(data.get("body") or "").endswith("\n") else "\n")
|
|
157
|
+
return 0
|
|
158
|
+
|
|
159
|
+
if action == "stat":
|
|
160
|
+
data = api_get("/fs/stat", {"path": args.path})
|
|
161
|
+
if is_json_mode():
|
|
162
|
+
output_json(data)
|
|
163
|
+
else:
|
|
164
|
+
_print_stat(console, data)
|
|
165
|
+
return 0
|
|
166
|
+
|
|
167
|
+
if action == "find":
|
|
168
|
+
data = api_get(
|
|
169
|
+
"/fs/find",
|
|
170
|
+
_params(
|
|
171
|
+
path=args.path,
|
|
172
|
+
type=getattr(args, "type", None),
|
|
173
|
+
label=getattr(args, "label", None),
|
|
174
|
+
since=getattr(args, "since", None),
|
|
175
|
+
until=getattr(args, "until", None),
|
|
176
|
+
mentions=getattr(args, "mentions", None),
|
|
177
|
+
limit=getattr(args, "limit", 50),
|
|
178
|
+
cursor=getattr(args, "cursor", None),
|
|
179
|
+
),
|
|
180
|
+
)
|
|
181
|
+
return _print_hits(console, output_json, is_json_mode, data, args=args)
|
|
182
|
+
|
|
183
|
+
if action == "grep":
|
|
184
|
+
data = api_get(
|
|
185
|
+
"/fs/grep",
|
|
186
|
+
_params(path=args.path, q=args.query, limit=args.limit, cursor=args.cursor),
|
|
187
|
+
)
|
|
188
|
+
if is_json_mode():
|
|
189
|
+
output_json(data)
|
|
190
|
+
elif getattr(args, "jsonl", False):
|
|
191
|
+
_print_jsonl(data.get("matches", []) or [])
|
|
192
|
+
elif getattr(args, "paths", False):
|
|
193
|
+
_print_unique_paths(data.get("matches", []) or [])
|
|
194
|
+
else:
|
|
195
|
+
for match in data.get("matches", []) or []:
|
|
196
|
+
console.print(f"{match.get('path')}:{match.get('line', 1)}:{match.get('match', '')}")
|
|
197
|
+
_print_next_cursor(console, data)
|
|
198
|
+
return 0
|
|
199
|
+
|
|
200
|
+
if action == "recall":
|
|
201
|
+
data = api_get("/fs/recall", _params(query=args.query, path=args.path, k=args.k))
|
|
202
|
+
return _print_hits(console, output_json, is_json_mode, data, args=args)
|
|
203
|
+
|
|
204
|
+
if action == "write":
|
|
205
|
+
frontmatter = _parse_frontmatter(getattr(args, "frontmatter_json", None))
|
|
206
|
+
body = _read_body(args)
|
|
207
|
+
data = api_post(
|
|
208
|
+
"/fs/write",
|
|
209
|
+
{
|
|
210
|
+
"path": args.path,
|
|
211
|
+
"body": body,
|
|
212
|
+
"frontmatter": frontmatter,
|
|
213
|
+
"record_version": getattr(args, "record_version", None),
|
|
214
|
+
},
|
|
215
|
+
)
|
|
216
|
+
if is_json_mode():
|
|
217
|
+
output_json(data)
|
|
218
|
+
else:
|
|
219
|
+
print_success("Wrote Nowledge FS path", args.path)
|
|
220
|
+
return 0
|
|
221
|
+
|
|
222
|
+
if action == "rm":
|
|
223
|
+
if not getattr(args, "force", False) and not is_json_mode():
|
|
224
|
+
print_error(
|
|
225
|
+
"Confirmation Required",
|
|
226
|
+
"Pass --force to delete through Nowledge FS.",
|
|
227
|
+
"Only canonical writable paths can be deleted.",
|
|
228
|
+
)
|
|
229
|
+
return 1
|
|
230
|
+
data = api_post("/fs/delete", {"path": args.path})
|
|
231
|
+
if is_json_mode():
|
|
232
|
+
output_json(data)
|
|
233
|
+
else:
|
|
234
|
+
print_success("Deleted Nowledge FS path", args.path)
|
|
235
|
+
return 0
|
|
236
|
+
|
|
237
|
+
print_error("Unknown fs verb", str(action), "Run nmem fs --help.")
|
|
238
|
+
return 1
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _params(**values: Any) -> dict[str, Any]:
|
|
242
|
+
return {key: value for key, value in values.items() if value is not None}
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _print_ls(console: Any, data: dict[str, Any], *, long: bool) -> None:
|
|
246
|
+
for entry in data.get("entries", []) or []:
|
|
247
|
+
kind = "dir" if entry.get("kind") in {"directory", "dir"} else "file"
|
|
248
|
+
name = entry.get("name") or entry.get("path")
|
|
249
|
+
if long:
|
|
250
|
+
hint = entry.get("hint") or entry.get("updated_at") or ""
|
|
251
|
+
console.print(f"{kind:4} {entry.get('path', name)} {hint}".rstrip())
|
|
252
|
+
else:
|
|
253
|
+
suffix = "/" if kind == "dir" else ""
|
|
254
|
+
console.print(f"{name}{suffix}")
|
|
255
|
+
_print_next_cursor(console, data)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _print_stat(console: Any, data: dict[str, Any]) -> None:
|
|
259
|
+
for key in ("path", "kind", "type", "id", "ext", "size_bytes", "created_at", "updated_at"):
|
|
260
|
+
if key in data and data.get(key) is not None:
|
|
261
|
+
console.print(f"{key}: {data[key]}")
|
|
262
|
+
fm = data.get("frontmatter")
|
|
263
|
+
if isinstance(fm, dict) and fm:
|
|
264
|
+
console.print("frontmatter:")
|
|
265
|
+
for key, value in fm.items():
|
|
266
|
+
console.print(f" {key}: {value}")
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _print_hits(
|
|
270
|
+
console: Any,
|
|
271
|
+
output_json: Callable[[Any], None],
|
|
272
|
+
is_json_mode: Callable[[], bool],
|
|
273
|
+
data: dict[str, Any],
|
|
274
|
+
*,
|
|
275
|
+
args: argparse.Namespace,
|
|
276
|
+
) -> int:
|
|
277
|
+
if is_json_mode():
|
|
278
|
+
output_json(data)
|
|
279
|
+
elif getattr(args, "jsonl", False):
|
|
280
|
+
_print_jsonl(data.get("paths") or data.get("hits") or [])
|
|
281
|
+
elif getattr(args, "paths", False):
|
|
282
|
+
_print_unique_paths(data.get("paths") or data.get("hits") or [])
|
|
283
|
+
else:
|
|
284
|
+
for hit in data.get("paths") or data.get("hits") or []:
|
|
285
|
+
line = str(hit.get("path") or "")
|
|
286
|
+
if hit.get("score") is not None:
|
|
287
|
+
line += f" score={hit.get('score')}"
|
|
288
|
+
console.print(line)
|
|
289
|
+
excerpt = hit.get("excerpt") or hit.get("snippet")
|
|
290
|
+
if excerpt:
|
|
291
|
+
console.print(f" {excerpt}")
|
|
292
|
+
_print_next_cursor(console, data)
|
|
293
|
+
return 0
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def _print_jsonl(rows: list[dict[str, Any]]) -> None:
|
|
297
|
+
for row in rows:
|
|
298
|
+
sys.stdout.write(json.dumps(row, ensure_ascii=False, separators=(",", ":")))
|
|
299
|
+
sys.stdout.write("\n")
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def _print_unique_paths(rows: list[dict[str, Any]]) -> None:
|
|
303
|
+
seen: set[str] = set()
|
|
304
|
+
for row in rows:
|
|
305
|
+
path = str(row.get("path") or "")
|
|
306
|
+
if not path or path in seen:
|
|
307
|
+
continue
|
|
308
|
+
seen.add(path)
|
|
309
|
+
sys.stdout.write(path)
|
|
310
|
+
sys.stdout.write("\n")
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _print_next_cursor(console: Any, data: dict[str, Any]) -> None:
|
|
314
|
+
cursor = data.get("next_cursor") or data.get("nextCursor")
|
|
315
|
+
if cursor:
|
|
316
|
+
console.print(f"[dim]next cursor: {cursor}[/dim]")
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _read_body(args: argparse.Namespace) -> str:
|
|
320
|
+
if getattr(args, "stdin", False):
|
|
321
|
+
return sys.stdin.read()
|
|
322
|
+
file_path = getattr(args, "file", None)
|
|
323
|
+
if file_path:
|
|
324
|
+
return file_path.read_text(encoding="utf-8")
|
|
325
|
+
return str(getattr(args, "body", "") or "")
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _parse_frontmatter(raw: str | None) -> dict[str, Any] | None:
|
|
329
|
+
if not raw:
|
|
330
|
+
return None
|
|
331
|
+
parsed = json.loads(raw)
|
|
332
|
+
if not isinstance(parsed, dict):
|
|
333
|
+
raise SystemExit("--frontmatter-json must be a JSON object")
|
|
334
|
+
return parsed
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|