@flydocs/cli 0.6.0-alpha.1 → 0.6.0-alpha.3
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/dist/cli.js +351 -225
- package/package.json +1 -1
- package/template/.claude/commands/flydocs-setup.md +52 -13
- package/template/.claude/commands/flydocs-upgrade.md +27 -15
- package/template/.claude/skills/flydocs-cloud/SKILL.md +18 -15
- package/template/.claude/skills/flydocs-cloud/scripts/create_issue.py +3 -0
- package/template/.claude/skills/flydocs-cloud/scripts/create_team.py +39 -0
- package/template/.claude/skills/flydocs-cloud/scripts/list_providers.py +19 -0
- package/template/.claude/skills/flydocs-cloud/scripts/set_provider.py +46 -0
- package/template/.flydocs/config.json +1 -1
- package/template/.flydocs/version +1 -1
- package/template/CHANGELOG.md +29 -0
- package/template/manifest.json +1 -1
package/package.json
CHANGED
|
@@ -301,7 +301,32 @@ FLYDOCS_API_KEY is not set. Run `flydocs connect` first to set up your API key.
|
|
|
301
301
|
|
|
302
302
|
Do not proceed until the key is available.
|
|
303
303
|
|
|
304
|
-
**Step 2:
|
|
304
|
+
**Step 2: Detect or select provider.**
|
|
305
|
+
|
|
306
|
+
Query connected providers:
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
python3 .claude/skills/flydocs-cloud/scripts/list_providers.py
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
This returns `[{type, name, connected}]`. Handle based on results:
|
|
313
|
+
|
|
314
|
+
- **One connected provider** — auto-select it and confirm with the user.
|
|
315
|
+
- **Multiple connected providers** — present a numbered list, let the user
|
|
316
|
+
choose which to use for this project.
|
|
317
|
+
- **No connected providers** — error: "No providers connected. Connect Linear
|
|
318
|
+
or Jira at app.flydocs.ai before running setup."
|
|
319
|
+
|
|
320
|
+
After selection, store the preference:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
python3 .claude/skills/flydocs-cloud/scripts/set_provider.py <provider_type>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
This stores the provider type on the relay (for routing) and updates
|
|
327
|
+
`provider.type` in local config.
|
|
328
|
+
|
|
329
|
+
**Step 3: Select or create team.**
|
|
305
330
|
|
|
306
331
|
If `provider.teamId` in config is null, discover available teams:
|
|
307
332
|
|
|
@@ -309,10 +334,23 @@ If `provider.teamId` in config is null, discover available teams:
|
|
|
309
334
|
python3 .claude/skills/flydocs-cloud/scripts/list_teams.py
|
|
310
335
|
```
|
|
311
336
|
|
|
312
|
-
Present the teams to the user as a numbered list showing name and key.
|
|
313
|
-
|
|
337
|
+
Present the teams to the user as a numbered list showing name and key.
|
|
338
|
+
Add a final option: **"Create a new team"**. If only one team exists,
|
|
339
|
+
confirm it.
|
|
340
|
+
|
|
341
|
+
If the user selects **"Create a new team"**:
|
|
342
|
+
|
|
343
|
+
1. Ask for team name (required) and key (optional — auto-generated if omitted)
|
|
344
|
+
2. Ask if this should be a sub-team under an existing team (show the list
|
|
345
|
+
again for parent selection, or "None — top-level team")
|
|
346
|
+
3. Create via:
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
python3 .claude/skills/flydocs-cloud/scripts/create_team.py --name "Team Name" [--key KEY] [--parent <parent_team_id>]
|
|
350
|
+
```
|
|
314
351
|
|
|
315
|
-
After selection, store the preference on the relay and in local
|
|
352
|
+
After selection or creation, store the preference on the relay and in local
|
|
353
|
+
config:
|
|
316
354
|
|
|
317
355
|
```bash
|
|
318
356
|
python3 .claude/skills/flydocs-cloud/scripts/set_team.py <team_id>
|
|
@@ -321,7 +359,7 @@ python3 .claude/skills/flydocs-cloud/scripts/set_team.py <team_id>
|
|
|
321
359
|
This stores the team preference server-side (the relay uses it for all
|
|
322
360
|
team-scoped operations) and updates `provider.teamId` in local config.
|
|
323
361
|
|
|
324
|
-
**Step
|
|
362
|
+
**Step 4: Get or create project.**
|
|
325
363
|
|
|
326
364
|
Query existing projects:
|
|
327
365
|
|
|
@@ -336,7 +374,7 @@ create a new project:
|
|
|
336
374
|
python3 .claude/skills/flydocs-cloud/scripts/create_project.py --name "Project Name"
|
|
337
375
|
```
|
|
338
376
|
|
|
339
|
-
**Step
|
|
377
|
+
**Step 5: Configure labels.**
|
|
340
378
|
|
|
341
379
|
Fetch available labels from the provider:
|
|
342
380
|
|
|
@@ -371,7 +409,7 @@ python3 .claude/skills/flydocs-cloud/scripts/set_labels.py \
|
|
|
371
409
|
If the relay returns `LABELS_NOT_FOUND`, show the invalid names and ask the
|
|
372
410
|
user to correct them.
|
|
373
411
|
|
|
374
|
-
**Step
|
|
412
|
+
**Step 6: Configure product identity.**
|
|
375
413
|
|
|
376
414
|
Ask about product metadata:
|
|
377
415
|
|
|
@@ -379,11 +417,11 @@ Ask about product metadata:
|
|
|
379
417
|
from project.md)
|
|
380
418
|
- **Icon and color** — optional, ask if they have preferences
|
|
381
419
|
|
|
382
|
-
**Step
|
|
420
|
+
**Step 7: Save to config.**
|
|
383
421
|
|
|
384
422
|
Update `.flydocs/config.json`:
|
|
385
423
|
|
|
386
|
-
- `provider.type` —
|
|
424
|
+
- `provider.type` — set by `set_provider.py`
|
|
387
425
|
- `provider.teamId` — selected team ID (set by `set_team.py`)
|
|
388
426
|
- `labels.defaults` — default label names (set by `set_labels.py`)
|
|
389
427
|
- `labels.typeMap` — type-to-label mapping (set by `set_labels.py`)
|
|
@@ -423,11 +461,11 @@ Let the user customize names, descriptions, and target dates.
|
|
|
423
461
|
|
|
424
462
|
**Step 4: Create milestones.**
|
|
425
463
|
|
|
426
|
-
For each approved milestone:
|
|
464
|
+
For each approved milestone, pass the project ID from Phase 2 Step 3:
|
|
427
465
|
|
|
428
466
|
```bash
|
|
429
467
|
python3 .claude/skills/flydocs-cloud/scripts/create_milestone.py \
|
|
430
|
-
--name "Phase 1: Foundation" --target-date YYYY-MM-DD
|
|
468
|
+
--name "Phase 1: Foundation" --project <project_id> --target-date YYYY-MM-DD
|
|
431
469
|
```
|
|
432
470
|
|
|
433
471
|
Record the milestone IDs for use in Phase 4.
|
|
@@ -455,10 +493,11 @@ For each work item, follow the capture procedure from
|
|
|
455
493
|
`.claude/skills/flydocs-workflow/stages/capture.md`:
|
|
456
494
|
|
|
457
495
|
- Determine type (feature, bug, chore, idea)
|
|
458
|
-
- Create via the mechanism script:
|
|
496
|
+
- Create via the mechanism script (cloud: pass `--project` from Phase 2):
|
|
459
497
|
```bash
|
|
460
498
|
python3 .claude/skills/flydocs-{tier}/scripts/create_issue.py \
|
|
461
|
-
--title "Issue title" --type feature --priority 3 --estimate 2
|
|
499
|
+
--title "Issue title" --type feature --priority 3 --estimate 2 \
|
|
500
|
+
--project <project_id>
|
|
462
501
|
```
|
|
463
502
|
- For quick ideas, use `--triage` flag
|
|
464
503
|
|
|
@@ -65,7 +65,7 @@ Count totals by status and type.
|
|
|
65
65
|
|
|
66
66
|
**Step 3: Check for API readiness.**
|
|
67
67
|
|
|
68
|
-
Check if `
|
|
68
|
+
Check if `FLYDOCS_API_KEY` is set in the environment (from `.env` or
|
|
69
69
|
`.env.local`). If not, warn the user they will need it for Phase 1.
|
|
70
70
|
|
|
71
71
|
**Step 4: Present migration summary.**
|
|
@@ -100,27 +100,39 @@ Confirm with the user before continuing.
|
|
|
100
100
|
## Phase 1: Connect to Cloud
|
|
101
101
|
|
|
102
102
|
Guide the user through connecting to the cloud provider. This swaps the
|
|
103
|
-
|
|
103
|
+
tier, stores the API key, and prepares the cloud mechanism skill.
|
|
104
104
|
|
|
105
|
-
**Step 1:
|
|
105
|
+
**Step 1: Get API key.**
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
Ask the user for their FlyDocs API key (`fdk_...`). If `FLYDOCS_API_KEY`
|
|
108
|
+
is already set in the environment, confirm they want to use the existing
|
|
109
|
+
key or enter a new one.
|
|
110
|
+
|
|
111
|
+
If no key is available, instruct the user:
|
|
109
112
|
|
|
110
113
|
```
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
- Prompt for your relay API key (from flydocs.ai account)
|
|
116
|
-
- Update .flydocs/config.json to cloud tier
|
|
117
|
-
- Swap mechanism skills (local -> cloud)
|
|
118
|
-
- Verify the connection works
|
|
114
|
+
You'll need a FlyDocs API key (fdk_...) from your dashboard at app.flydocs.ai.
|
|
115
|
+
|
|
116
|
+
Option A: Run `flydocs connect --here` in your terminal (handles everything)
|
|
117
|
+
Option B: Add FLYDOCS_API_KEY=fdk_... to your .env or .env.local file
|
|
119
118
|
```
|
|
120
119
|
|
|
121
|
-
|
|
120
|
+
Wait for the user to confirm the key is set before proceeding.
|
|
121
|
+
|
|
122
|
+
**Step 2: Update config to cloud tier.**
|
|
123
|
+
|
|
124
|
+
Read `.flydocs/config.json`, update `tier` to `"cloud"`, and write it back.
|
|
125
|
+
Preserve all existing config values (detected stack, skills, etc.).
|
|
126
|
+
|
|
127
|
+
**Step 3: Swap mechanism skill.**
|
|
128
|
+
|
|
129
|
+
Instruct the user to run `flydocs connect --here` from their terminal if
|
|
130
|
+
they haven't already. This handles the mechanism skill swap (installs
|
|
131
|
+
`flydocs-cloud`, removes `flydocs-local`).
|
|
132
|
+
|
|
133
|
+
**Step 4: Verify connection.**
|
|
122
134
|
|
|
123
|
-
After the user confirms
|
|
135
|
+
After the user confirms the swap, verify:
|
|
124
136
|
|
|
125
137
|
1. Read `.flydocs/config.json` — `tier` should now be `cloud`
|
|
126
138
|
2. Check that `.claude/skills/flydocs-cloud/scripts/` exists
|
|
@@ -29,15 +29,15 @@ All scripts: `python3 .claude/skills/flydocs-cloud/scripts/<script>`
|
|
|
29
29
|
|
|
30
30
|
### Shared Contract Scripts
|
|
31
31
|
|
|
32
|
-
| Script | Usage
|
|
33
|
-
| ----------------------- |
|
|
34
|
-
| `create_issue.py` | `--title "..." --type feature [--description "..."] [--description-file PATH] [--priority 0-4] [--estimate 1-5] [--assignee STR] [--labels "a,b"] [--triage] \| stdin` | `{id, identifier, title, url}` |
|
|
35
|
-
| `transition.py` | `<ref> <STATUS> "<comment>"`
|
|
36
|
-
| `comment.py` | `<ref> ["<comment>"] \| stdin`
|
|
37
|
-
| `list_issues.py` | `[--status STATUS[,STATUS]] [--active] [--project ID] [--milestone ID] [--assignee STR] [--mine] [--limit N]`
|
|
38
|
-
| `get_issue.py` | `<ref> [--fields basic\|full]`
|
|
39
|
-
| `assign.py` | `<ref> <assignee>`
|
|
40
|
-
| `update_description.py` | `<ref> --text "..." \| --file PATH \| stdin`
|
|
32
|
+
| Script | Usage | Output |
|
|
33
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
34
|
+
| `create_issue.py` | `--title "..." --type feature [--description "..."] [--description-file PATH] [--priority 0-4] [--estimate 1-5] [--assignee STR] [--project ID] [--labels "a,b"] [--triage] \| stdin` | `{id, identifier, title, url}` |
|
|
35
|
+
| `transition.py` | `<ref> <STATUS> "<comment>"` | `{success, issue, previousStatus, newStatus}` |
|
|
36
|
+
| `comment.py` | `<ref> ["<comment>"] \| stdin` | `{success, commentId}` |
|
|
37
|
+
| `list_issues.py` | `[--status STATUS[,STATUS]] [--active] [--project ID] [--milestone ID] [--assignee STR] [--mine] [--limit N]` | `[{id, identifier, title, status, assignee, priority, dueDate, milestone, milestoneId, milestoneSortOrder, project, projectId}]` |
|
|
38
|
+
| `get_issue.py` | `<ref> [--fields basic\|full]` | `{id, identifier, title, description, status, assignee, priority, estimate, dueDate, milestone, milestoneId, project, projectId, comments[]}` |
|
|
39
|
+
| `assign.py` | `<ref> <assignee>` | `{success, issue, assignee}` |
|
|
40
|
+
| `update_description.py` | `<ref> --text "..." \| --file PATH \| stdin` | `{success, issue}` |
|
|
41
41
|
|
|
42
42
|
### Extended Scripts
|
|
43
43
|
|
|
@@ -58,12 +58,15 @@ All scripts: `python3 .claude/skills/flydocs-cloud/scripts/<script>`
|
|
|
58
58
|
|
|
59
59
|
### Workspace Scripts
|
|
60
60
|
|
|
61
|
-
| Script
|
|
62
|
-
|
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
61
|
+
| Script | Usage | Output |
|
|
62
|
+
| ------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------- |
|
|
63
|
+
| `list_providers.py` | (no args) | `[{type, name, connected}]` |
|
|
64
|
+
| `set_provider.py` | `<provider_type>` (`linear` or `jira`) | `{success}` — updates relay routing and local config `provider.type` |
|
|
65
|
+
| `list_teams.py` | (no args) | `[{id, name, key}]` |
|
|
66
|
+
| `create_team.py` | `--name "..." [--key KEY] [--description "..."] [--parent <team_id>]` | `{id, name, key}` — `--parent` creates a sub-team |
|
|
67
|
+
| `set_team.py` | `<team_id>` | `{success}` — updates relay preference and local config `provider.teamId` |
|
|
68
|
+
| `list_labels.py` | (no args) | `[{id, name, color}]` — requires team to be set first |
|
|
69
|
+
| `set_labels.py` | `--defaults '["a"]' --type-map '{"feature":["F"],...}' \| stdin` | `{success, validated, defaults, typeMap}` — stores label config on relay |
|
|
67
70
|
|
|
68
71
|
### Script Notes
|
|
69
72
|
|
|
@@ -18,6 +18,7 @@ def main():
|
|
|
18
18
|
parser.add_argument("--priority", type=int, default=3, choices=range(5))
|
|
19
19
|
parser.add_argument("--estimate", type=int, default=0, choices=[0, 1, 2, 3, 5])
|
|
20
20
|
parser.add_argument("--assignee", default=None)
|
|
21
|
+
parser.add_argument("--project", default=None, help="Project ID")
|
|
21
22
|
parser.add_argument("--labels", default=None, help="Comma-separated ad-hoc label names")
|
|
22
23
|
parser.add_argument("--triage", action="store_true")
|
|
23
24
|
args = parser.parse_args()
|
|
@@ -43,6 +44,8 @@ def main():
|
|
|
43
44
|
body["estimate"] = args.estimate
|
|
44
45
|
if args.assignee:
|
|
45
46
|
body["assignee"] = args.assignee
|
|
47
|
+
if args.project:
|
|
48
|
+
body["projectId"] = args.project
|
|
46
49
|
if args.labels:
|
|
47
50
|
body["labels"] = [l.strip() for l in args.labels.split(",") if l.strip()]
|
|
48
51
|
if args.triage:
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Create a team (or sub-team) via the FlyDocs Relay API."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
9
|
+
from flydocs_api import get_client, output_json
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
parser = argparse.ArgumentParser(description="Create team")
|
|
14
|
+
parser.add_argument("--name", required=True)
|
|
15
|
+
parser.add_argument("--key", default=None, help="Team key (e.g., PROD). Auto-generated if omitted.")
|
|
16
|
+
parser.add_argument("--description", default=None)
|
|
17
|
+
parser.add_argument("--parent", default=None, dest="parent_id", help="Parent team ID to create a sub-team")
|
|
18
|
+
args = parser.parse_args()
|
|
19
|
+
|
|
20
|
+
body: dict = {"name": args.name}
|
|
21
|
+
if args.key:
|
|
22
|
+
body["key"] = args.key
|
|
23
|
+
if args.description:
|
|
24
|
+
body["description"] = args.description
|
|
25
|
+
if args.parent_id:
|
|
26
|
+
body["parentId"] = args.parent_id
|
|
27
|
+
|
|
28
|
+
client = get_client()
|
|
29
|
+
result = client.post("/teams", body)
|
|
30
|
+
|
|
31
|
+
output_json({
|
|
32
|
+
"id": result["id"],
|
|
33
|
+
"name": result["name"],
|
|
34
|
+
"key": result.get("key", ""),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
main()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""List available providers via the FlyDocs Relay API."""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
8
|
+
from flydocs_api import get_client, output_json
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
client = get_client()
|
|
13
|
+
result = client.get("/providers")
|
|
14
|
+
|
|
15
|
+
output_json(result)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
main()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Set provider preference via the FlyDocs Relay API.
|
|
3
|
+
|
|
4
|
+
Stores the provider type on the relay (for server-side routing)
|
|
5
|
+
and updates the local config (for display/reference).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import json
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
14
|
+
from flydocs_api import get_client, output_json, fail
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def main():
|
|
18
|
+
parser = argparse.ArgumentParser(description="Set provider preference")
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"provider_type",
|
|
21
|
+
choices=["linear", "jira"],
|
|
22
|
+
help="Provider type",
|
|
23
|
+
)
|
|
24
|
+
args = parser.parse_args()
|
|
25
|
+
|
|
26
|
+
client = get_client()
|
|
27
|
+
result = client.post("/auth/provider", {"providerType": args.provider_type})
|
|
28
|
+
|
|
29
|
+
# Update local config with provider type
|
|
30
|
+
config_path = client.config_path
|
|
31
|
+
if config_path.exists():
|
|
32
|
+
with open(config_path, "r") as f:
|
|
33
|
+
config = json.load(f)
|
|
34
|
+
if "provider" not in config:
|
|
35
|
+
config["provider"] = {"type": args.provider_type, "teamId": None}
|
|
36
|
+
else:
|
|
37
|
+
config["provider"]["type"] = args.provider_type
|
|
38
|
+
with open(config_path, "w") as f:
|
|
39
|
+
json.dump(config, f, indent=2)
|
|
40
|
+
f.write("\n")
|
|
41
|
+
|
|
42
|
+
output_json(result)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
main()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.6.0-alpha.
|
|
1
|
+
0.6.0-alpha.3
|
package/template/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,35 @@ Versioning: [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.6.0-alpha.3] — 2026-03-13
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Provider selection** — new `list_providers.py` and `set_provider.py`
|
|
15
|
+
scripts for multi-provider support (Linear + Jira). Setup flow detects
|
|
16
|
+
connected providers before team selection.
|
|
17
|
+
- **Inline API key prompt** — `flydocs install` now prompts for FlyDocs API
|
|
18
|
+
key when cloud tier is selected. No separate `flydocs connect` step needed.
|
|
19
|
+
- **Existing config detection** — install detects pre-existing `.claude/CLAUDE.md`,
|
|
20
|
+
`settings.json`, `hooks.json`, and `AGENTS.md`. Prompts user to overwrite
|
|
21
|
+
(with backup) or preserve.
|
|
22
|
+
- **`--project` flag** — `create_issue.py` accepts `--project <id>` to scope
|
|
23
|
+
issues to a specific project. Fixes milestone/project mismatch during setup.
|
|
24
|
+
- **Team creation** — `create_team.py` with `--parent` flag for sub-teams.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **Setup flow restructured** — Phase 2 now starts with provider detection,
|
|
29
|
+
then team selection, project, labels. Milestones and issues pass `--project`.
|
|
30
|
+
- **Community skill output** — install shows one line per skill instead of
|
|
31
|
+
4-5 verbose lines. "No triggers" warning suppressed for community skills.
|
|
32
|
+
- **`/flydocs-upgrade` streamlined** — Phase 1 inlines API key handling
|
|
33
|
+
instead of requiring a separate terminal step.
|
|
34
|
+
- **API key helpers extracted** — shared `api-key.ts` module used by both
|
|
35
|
+
`install` and `connect` commands.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
10
39
|
## [0.6.0-alpha.1] — 2026-03-12
|
|
11
40
|
|
|
12
41
|
### Added
|
package/template/manifest.json
CHANGED