@flydocs/cli 0.5.0-beta.9 → 0.6.0-alpha.10
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/README.md +6 -0
- package/dist/cli.js +1553 -414
- package/package.json +1 -1
- package/template/.claude/CLAUDE.md +11 -9
- package/template/.claude/agents/implementation-agent.md +0 -1
- package/template/.claude/agents/pm-agent.md +0 -1
- package/template/.claude/agents/research-agent.md +0 -1
- package/template/.claude/agents/review-agent.md +0 -1
- package/template/.claude/commands/flydocs-setup.md +202 -35
- package/template/.claude/commands/flydocs-upgrade.md +342 -0
- package/template/.claude/commands/knowledge.md +61 -0
- package/template/.claude/skills/flydocs-cloud/SKILL.md +66 -39
- package/template/.claude/skills/flydocs-cloud/cursor-rule.mdc +5 -5
- package/template/.claude/skills/flydocs-cloud/scripts/assign.py +17 -27
- package/template/.claude/skills/flydocs-cloud/scripts/assign_cycle.py +14 -30
- package/template/.claude/skills/flydocs-cloud/scripts/assign_milestone.py +10 -32
- package/template/.claude/skills/flydocs-cloud/scripts/comment.py +15 -25
- package/template/.claude/skills/flydocs-cloud/scripts/create_issue.py +42 -59
- package/template/.claude/skills/flydocs-cloud/scripts/create_milestone.py +26 -37
- package/template/.claude/skills/flydocs-cloud/scripts/create_project.py +24 -31
- package/template/.claude/skills/flydocs-cloud/scripts/create_team.py +39 -0
- package/template/.claude/skills/flydocs-cloud/scripts/delete_milestone.py +21 -0
- package/template/.claude/skills/flydocs-cloud/scripts/estimate.py +17 -22
- package/template/.claude/skills/flydocs-cloud/scripts/flydocs_api.py +113 -169
- package/template/.claude/skills/flydocs-cloud/scripts/get_estimate_scale.py +23 -0
- package/template/.claude/skills/flydocs-cloud/scripts/get_issue.py +6 -59
- package/template/.claude/skills/flydocs-cloud/scripts/link.py +16 -35
- package/template/.claude/skills/flydocs-cloud/scripts/list_cycles.py +21 -28
- package/template/.claude/skills/flydocs-cloud/scripts/list_issues.py +16 -77
- package/template/.claude/skills/flydocs-cloud/scripts/list_labels.py +19 -0
- package/template/.claude/skills/flydocs-cloud/scripts/list_milestones.py +21 -33
- package/template/.claude/skills/flydocs-cloud/scripts/list_projects.py +24 -38
- package/template/.claude/skills/flydocs-cloud/scripts/list_providers.py +19 -0
- package/template/.claude/skills/flydocs-cloud/scripts/list_statuses.py +19 -0
- package/template/.claude/skills/flydocs-cloud/scripts/list_teams.py +19 -0
- package/template/.claude/skills/flydocs-cloud/scripts/priority.py +10 -19
- package/template/.claude/skills/flydocs-cloud/scripts/project_update.py +36 -50
- package/template/.claude/skills/flydocs-cloud/scripts/refresh_labels.py +87 -0
- package/template/.claude/skills/flydocs-cloud/scripts/set_identity.py +38 -0
- package/template/.claude/skills/flydocs-cloud/scripts/set_labels.py +68 -0
- package/template/.claude/skills/flydocs-cloud/scripts/set_preferences.py +49 -0
- package/template/.claude/skills/flydocs-cloud/scripts/set_provider.py +46 -0
- package/template/.claude/skills/flydocs-cloud/scripts/set_status_mapping.py +69 -0
- package/template/.claude/skills/flydocs-cloud/scripts/set_team.py +42 -0
- package/template/.claude/skills/flydocs-cloud/scripts/transition.py +11 -52
- package/template/.claude/skills/flydocs-cloud/scripts/update_description.py +16 -27
- package/template/.claude/skills/flydocs-cloud/scripts/update_issue.py +43 -54
- package/template/.claude/skills/flydocs-cloud/scripts/update_milestone.py +42 -0
- package/template/.claude/skills/flydocs-cloud/scripts/validate_setup.py +139 -0
- package/template/.claude/skills/flydocs-local/SKILL.md +1 -1
- package/template/.claude/skills/flydocs-local/scripts/assign.py +13 -4
- package/template/.claude/skills/flydocs-local/scripts/flydocs_api.py +5 -2
- package/template/.claude/skills/flydocs-workflow/SKILL.md +23 -18
- package/template/.claude/skills/flydocs-workflow/reference/comment-templates.md +1 -0
- package/template/.claude/skills/flydocs-workflow/reference/pr-workflow.md +105 -0
- package/template/.claude/skills/flydocs-workflow/reference/priority-estimates.md +37 -15
- package/template/.claude/skills/flydocs-workflow/session.md +24 -16
- package/template/.claude/skills/flydocs-workflow/stages/capture.md +8 -3
- package/template/.claude/skills/flydocs-workflow/stages/close.md +4 -3
- package/template/.claude/skills/flydocs-workflow/stages/implement.md +28 -4
- package/template/.claude/skills/flydocs-workflow/stages/refine.md +20 -4
- package/template/.claude/skills/flydocs-workflow/stages/review.md +14 -2
- package/template/.env.example +16 -7
- package/template/.flydocs/config.json +4 -18
- package/template/.flydocs/hooks/prompt-submit.py +27 -4
- package/template/.flydocs/version +1 -1
- package/template/AGENTS.md +8 -8
- package/template/CHANGELOG.md +183 -0
- package/template/flydocs/knowledge/INDEX.md +38 -53
- package/template/flydocs/knowledge/README.md +60 -9
- package/template/flydocs/knowledge/templates/decision.md +47 -0
- package/template/flydocs/knowledge/templates/feature.md +35 -0
- package/template/flydocs/knowledge/templates/note.md +25 -0
- package/template/manifest.json +12 -4
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""List issues with optional filters."""
|
|
2
|
+
"""List issues with optional filters via the FlyDocs Relay API."""
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
5
|
import sys
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
8
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
9
|
-
from flydocs_api import get_client, output_json
|
|
9
|
+
from flydocs_api import get_client, output_json
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def main():
|
|
13
13
|
parser = argparse.ArgumentParser(description="List issues")
|
|
14
14
|
parser.add_argument("--status", default="", help="Single status or comma-separated: READY,IMPLEMENTING,BLOCKED")
|
|
15
|
-
parser.add_argument("--active", action="store_true", help="All non-terminal states
|
|
15
|
+
parser.add_argument("--active", action="store_true", help="All non-terminal states")
|
|
16
16
|
parser.add_argument("--project", default="")
|
|
17
17
|
parser.add_argument("--assignee", default="")
|
|
18
18
|
parser.add_argument("--milestone", default="", help="Filter by milestone ID")
|
|
@@ -20,85 +20,24 @@ def main():
|
|
|
20
20
|
parser.add_argument("--limit", type=int, default=50)
|
|
21
21
|
args = parser.parse_args()
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
filters = {"team": {"id": {"eq": client.team_id}}}
|
|
27
|
-
|
|
28
|
-
if args.project:
|
|
29
|
-
filters["project"] = {"id": {"eq": args.project}}
|
|
30
|
-
|
|
23
|
+
params: dict = {"limit": str(args.limit)}
|
|
24
|
+
if args.status:
|
|
25
|
+
params["status"] = args.status.upper()
|
|
31
26
|
if args.active:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
filters["state"] = {"id": {"nin": terminal_ids}}
|
|
38
|
-
elif args.status:
|
|
39
|
-
statuses = [s.strip().upper() for s in args.status.split(",")]
|
|
40
|
-
if len(statuses) == 1:
|
|
41
|
-
state_id = client.get_state_id(statuses[0])
|
|
42
|
-
if state_id:
|
|
43
|
-
filters["state"] = {"id": {"eq": state_id}}
|
|
44
|
-
else:
|
|
45
|
-
state_ids = [client.get_state_id(s) for s in statuses]
|
|
46
|
-
state_ids = [sid for sid in state_ids if sid]
|
|
47
|
-
if state_ids:
|
|
48
|
-
filters["state"] = {"id": {"in": state_ids}}
|
|
49
|
-
|
|
27
|
+
params["active"] = "true"
|
|
28
|
+
if args.project:
|
|
29
|
+
params["project"] = args.project
|
|
30
|
+
if args.assignee:
|
|
31
|
+
params["assignee"] = args.assignee
|
|
50
32
|
if args.milestone:
|
|
51
|
-
|
|
52
|
-
|
|
33
|
+
params["milestone"] = args.milestone
|
|
53
34
|
if args.mine:
|
|
54
|
-
|
|
55
|
-
user_id = viewer.get("data", {}).get("viewer", {}).get("id")
|
|
56
|
-
if user_id:
|
|
57
|
-
filters["assignee"] = {"id": {"eq": user_id}}
|
|
58
|
-
elif args.assignee:
|
|
59
|
-
user_id, _ = client.resolve_user_id(args.assignee)
|
|
60
|
-
if user_id:
|
|
61
|
-
filters["assignee"] = {"id": {"eq": user_id}}
|
|
35
|
+
params["mine"] = "true"
|
|
62
36
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
filters = client.build_product_scope(filters)
|
|
66
|
-
|
|
67
|
-
result = client.query(
|
|
68
|
-
"""query($filter: IssueFilter!, $limit: Int!) {
|
|
69
|
-
issues(filter: $filter, first: $limit, orderBy: updatedAt) {
|
|
70
|
-
nodes {
|
|
71
|
-
id identifier title
|
|
72
|
-
state { name }
|
|
73
|
-
assignee { name }
|
|
74
|
-
priority dueDate
|
|
75
|
-
project { id name }
|
|
76
|
-
projectMilestone { id name sortOrder }
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}""",
|
|
80
|
-
{"filter": filters, "limit": args.limit},
|
|
81
|
-
)
|
|
37
|
+
client = get_client()
|
|
38
|
+
result = client.get("/issues", params=params)
|
|
82
39
|
|
|
83
|
-
|
|
84
|
-
issues = [
|
|
85
|
-
{
|
|
86
|
-
"id": n["id"],
|
|
87
|
-
"identifier": n["identifier"],
|
|
88
|
-
"title": n["title"],
|
|
89
|
-
"status": n.get("state", {}).get("name", ""),
|
|
90
|
-
"assignee": (n.get("assignee") or {}).get("name", ""),
|
|
91
|
-
"priority": n.get("priority", 0),
|
|
92
|
-
"dueDate": n.get("dueDate") or "",
|
|
93
|
-
"milestone": (n.get("projectMilestone") or {}).get("name", ""),
|
|
94
|
-
"milestoneId": (n.get("projectMilestone") or {}).get("id", ""),
|
|
95
|
-
"milestoneSortOrder": (n.get("projectMilestone") or {}).get("sortOrder", 0),
|
|
96
|
-
"project": (n.get("project") or {}).get("name", ""),
|
|
97
|
-
"projectId": (n.get("project") or {}).get("id", ""),
|
|
98
|
-
}
|
|
99
|
-
for n in nodes
|
|
100
|
-
]
|
|
101
|
-
output_json(issues)
|
|
40
|
+
output_json(result)
|
|
102
41
|
|
|
103
42
|
|
|
104
43
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""List available team labels 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("/labels")
|
|
14
|
+
|
|
15
|
+
output_json(result)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
main()
|
|
@@ -1,40 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""List milestones."""
|
|
2
|
+
"""List milestones via the FlyDocs Relay API."""
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
5
|
import sys
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
8
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
9
|
-
from flydocs_api import get_client, output_json
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"id": n["id"],
|
|
30
|
-
"name": n["name"],
|
|
31
|
-
"sortOrder": n.get("sortOrder", 0),
|
|
32
|
-
"targetDate": n.get("targetDate", ""),
|
|
33
|
-
"project": (n.get("project") or {}).get("name", ""),
|
|
34
|
-
"projectId": (n.get("project") or {}).get("id", ""),
|
|
35
|
-
}
|
|
36
|
-
for n in nodes
|
|
37
|
-
]
|
|
38
|
-
# Sort by project then sortOrder for consistent display
|
|
39
|
-
milestones.sort(key=lambda m: (m["project"], m["sortOrder"]))
|
|
40
|
-
output_json(milestones)
|
|
9
|
+
from flydocs_api import get_client, output_json
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
parser = argparse.ArgumentParser(description="List milestones")
|
|
14
|
+
parser.add_argument("--all", action="store_true", dest="show_all")
|
|
15
|
+
args = parser.parse_args()
|
|
16
|
+
|
|
17
|
+
params: dict = {}
|
|
18
|
+
if args.show_all:
|
|
19
|
+
params["all"] = "true"
|
|
20
|
+
|
|
21
|
+
client = get_client()
|
|
22
|
+
result = client.get("/milestones", params=params)
|
|
23
|
+
|
|
24
|
+
output_json(result)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
main()
|
|
@@ -1,45 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""List projects."""
|
|
2
|
+
"""List projects via the FlyDocs Relay API."""
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
5
|
import sys
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
8
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
9
|
-
from flydocs_api import get_client, output_json
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
parser
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
filters["id"] = {"in": active_projects}
|
|
33
|
-
|
|
34
|
-
result = client.query(
|
|
35
|
-
"""query($filter: ProjectFilter!) {
|
|
36
|
-
projects(filter: $filter, first: 50, orderBy: updatedAt) {
|
|
37
|
-
nodes { id name state description }
|
|
38
|
-
}
|
|
39
|
-
}""",
|
|
40
|
-
{"filter": filters},
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
nodes = result.get("data", {}).get("projects", {}).get("nodes", [])
|
|
44
|
-
projects = [{"id": n["id"], "name": n["name"], "state": n.get("state", "")} for n in nodes]
|
|
45
|
-
output_json(projects)
|
|
9
|
+
from flydocs_api import get_client, output_json
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
parser = argparse.ArgumentParser(description="List projects")
|
|
14
|
+
parser.add_argument("--active", action="store_true")
|
|
15
|
+
parser.add_argument("--all", action="store_true", dest="show_all")
|
|
16
|
+
args = parser.parse_args()
|
|
17
|
+
|
|
18
|
+
params: dict = {}
|
|
19
|
+
if args.active:
|
|
20
|
+
params["active"] = "true"
|
|
21
|
+
if args.show_all:
|
|
22
|
+
params["all"] = "true"
|
|
23
|
+
|
|
24
|
+
client = get_client()
|
|
25
|
+
result = client.get("/projects", params=params)
|
|
26
|
+
|
|
27
|
+
output_json(result)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
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,19 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""List provider workflow states 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("/auth/statuses")
|
|
14
|
+
|
|
15
|
+
output_json(result)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
main()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""List available teams/projects 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("/teams")
|
|
14
|
+
|
|
15
|
+
output_json(result)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
main()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Set priority on an issue."""
|
|
2
|
+
"""Set priority on an issue via the FlyDocs Relay API."""
|
|
3
3
|
|
|
4
4
|
import sys
|
|
5
5
|
from pathlib import Path
|
|
@@ -16,23 +16,14 @@ try:
|
|
|
16
16
|
except ValueError:
|
|
17
17
|
fail("Priority must be a number (0-4)")
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if not issue_uuid:
|
|
22
|
-
fail(f"Issue not found: {ref}")
|
|
23
|
-
|
|
24
|
-
result = client.query(
|
|
25
|
-
"""mutation($id: String!, $priority: Int!) {
|
|
26
|
-
issueUpdate(id: $id, input: { priority: $priority }) {
|
|
27
|
-
success
|
|
28
|
-
issue { identifier priority }
|
|
29
|
-
}
|
|
30
|
-
}""",
|
|
31
|
-
{"id": issue_uuid, "priority": priority},
|
|
32
|
-
)
|
|
19
|
+
if priority not in range(5):
|
|
20
|
+
fail("Priority must be 0 (none), 1 (urgent), 2 (high), 3 (medium), or 4 (low)")
|
|
33
21
|
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
client = get_client()
|
|
23
|
+
result = client.put(f"/issues/{ref}/priority", {"priority": priority})
|
|
36
24
|
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
output_json({
|
|
26
|
+
"success": result.get("success", True),
|
|
27
|
+
"issue": result.get("issue", ref),
|
|
28
|
+
"priority": result.get("priority", priority),
|
|
29
|
+
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Post a project
|
|
2
|
+
"""Post a project update via the FlyDocs Relay API."""
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
5
|
import sys
|
|
@@ -8,52 +8,38 @@ from pathlib import Path
|
|
|
8
8
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
9
9
|
from flydocs_api import get_client, output_json, fail
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
parser = argparse.ArgumentParser(description="Post project update")
|
|
14
|
-
parser.add_argument("--health", required=True, choices=
|
|
15
|
-
parser.add_argument("--body", default=
|
|
16
|
-
parser.add_argument("--body-file", default="")
|
|
17
|
-
parser.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
body = args.body
|
|
21
|
-
if args.body_file:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
health: $health
|
|
47
|
-
}) {
|
|
48
|
-
success
|
|
49
|
-
projectUpdate { id }
|
|
50
|
-
}
|
|
51
|
-
}""",
|
|
52
|
-
{"projectId": project_id, "body": body, "health": HEALTH_MAP[args.health]},
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
data = result.get("data", {}).get("projectUpdateCreate", {})
|
|
56
|
-
if not data.get("success"):
|
|
57
|
-
fail(f"Failed to post update: {result}")
|
|
58
|
-
|
|
59
|
-
output_json({"success": True, "id": data.get("projectUpdate", {}).get("id", "")})
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
parser = argparse.ArgumentParser(description="Post project update")
|
|
14
|
+
parser.add_argument("--health", required=True, choices=["onTrack", "atRisk", "offTrack"])
|
|
15
|
+
parser.add_argument("--body", default=None)
|
|
16
|
+
parser.add_argument("--body-file", default=None, dest="body_file")
|
|
17
|
+
args = parser.parse_args()
|
|
18
|
+
|
|
19
|
+
# Resolve body: --body-file > stdin > --body
|
|
20
|
+
body = args.body
|
|
21
|
+
if args.body_file:
|
|
22
|
+
try:
|
|
23
|
+
body = Path(args.body_file).read_text()
|
|
24
|
+
except FileNotFoundError:
|
|
25
|
+
fail(f"File not found: {args.body_file}")
|
|
26
|
+
elif body is None and not sys.stdin.isatty():
|
|
27
|
+
body = sys.stdin.read().strip()
|
|
28
|
+
|
|
29
|
+
if not body:
|
|
30
|
+
fail("Provide body via --body, --body-file, or stdin")
|
|
31
|
+
|
|
32
|
+
client = get_client()
|
|
33
|
+
result = client.post("/projects/update", {
|
|
34
|
+
"health": args.health,
|
|
35
|
+
"body": body,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
output_json({
|
|
39
|
+
"success": result.get("success", True),
|
|
40
|
+
"id": result.get("id", ""),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
main()
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Refresh label config from the relay — validates and updates local config.
|
|
3
|
+
|
|
4
|
+
Fetches current team labels from the relay, compares against local config,
|
|
5
|
+
and reports any stale or missing label IDs. With --fix, updates local config
|
|
6
|
+
to match the relay's current state.
|
|
7
|
+
"""
|
|
8
|
+
|
|
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
|
+
fix_mode = "--fix" in sys.argv
|
|
19
|
+
|
|
20
|
+
client = get_client()
|
|
21
|
+
|
|
22
|
+
# Fetch current labels from relay
|
|
23
|
+
labels = client.get("/labels")
|
|
24
|
+
label_map = {l["id"]: l["name"] for l in labels}
|
|
25
|
+
label_by_name = {l["name"].lower(): l["id"] for l in labels}
|
|
26
|
+
|
|
27
|
+
# Load local config
|
|
28
|
+
config_path = client.config_path
|
|
29
|
+
if not config_path.exists():
|
|
30
|
+
fail("No .flydocs/config.json found")
|
|
31
|
+
|
|
32
|
+
with open(config_path, "r") as f:
|
|
33
|
+
config = json.load(f)
|
|
34
|
+
|
|
35
|
+
issue_labels = config.get("issueLabels", {})
|
|
36
|
+
stale: list[dict] = []
|
|
37
|
+
valid: list[dict] = []
|
|
38
|
+
|
|
39
|
+
# Check each label ID in config against relay
|
|
40
|
+
for category, entries in issue_labels.items():
|
|
41
|
+
if isinstance(entries, dict):
|
|
42
|
+
for key, label_id in entries.items():
|
|
43
|
+
if label_id in label_map:
|
|
44
|
+
valid.append({"category": category, "key": key, "id": label_id, "name": label_map[label_id]})
|
|
45
|
+
else:
|
|
46
|
+
# Try to find by key name
|
|
47
|
+
resolved = label_by_name.get(key.lower())
|
|
48
|
+
stale.append({
|
|
49
|
+
"category": category,
|
|
50
|
+
"key": key,
|
|
51
|
+
"staleId": label_id,
|
|
52
|
+
"resolvedId": resolved,
|
|
53
|
+
"resolvedName": key if resolved else None,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
if fix_mode and stale:
|
|
57
|
+
# Update stale IDs in config
|
|
58
|
+
fixed = 0
|
|
59
|
+
for item in stale:
|
|
60
|
+
if item["resolvedId"]:
|
|
61
|
+
issue_labels[item["category"]][item["key"]] = item["resolvedId"]
|
|
62
|
+
fixed += 1
|
|
63
|
+
|
|
64
|
+
with open(config_path, "w") as f:
|
|
65
|
+
json.dump(config, f, indent=2)
|
|
66
|
+
f.write("\n")
|
|
67
|
+
|
|
68
|
+
output_json({
|
|
69
|
+
"success": True,
|
|
70
|
+
"valid": len(valid),
|
|
71
|
+
"stale": len(stale),
|
|
72
|
+
"fixed": fixed,
|
|
73
|
+
"unfixable": len(stale) - fixed,
|
|
74
|
+
"details": stale,
|
|
75
|
+
})
|
|
76
|
+
else:
|
|
77
|
+
output_json({
|
|
78
|
+
"valid": len(valid),
|
|
79
|
+
"stale": len(stale),
|
|
80
|
+
"totalProviderLabels": len(labels),
|
|
81
|
+
"details": stale if stale else "All label IDs are current",
|
|
82
|
+
"hint": "Run with --fix to update stale IDs" if stale else None,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
main()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Set provider identity via the FlyDocs Relay API.
|
|
3
|
+
|
|
4
|
+
Binds the user's provider-specific ID to their FlyDocs user record.
|
|
5
|
+
Once set, ?mine=true resolves via exact provider ID matching.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
12
|
+
from flydocs_api import get_client, output_json, fail
|
|
13
|
+
|
|
14
|
+
VALID_PROVIDERS = ("linear", "jira", "gitlab")
|
|
15
|
+
|
|
16
|
+
if len(sys.argv) < 3:
|
|
17
|
+
fail(f"Usage: set_identity.py <provider> <provider-user-id>\n Providers: {', '.join(VALID_PROVIDERS)}")
|
|
18
|
+
|
|
19
|
+
provider = sys.argv[1].lower()
|
|
20
|
+
provider_id = sys.argv[2]
|
|
21
|
+
|
|
22
|
+
if provider not in VALID_PROVIDERS:
|
|
23
|
+
fail(f"Invalid provider: {provider}. Must be one of: {', '.join(VALID_PROVIDERS)}")
|
|
24
|
+
|
|
25
|
+
if not provider_id:
|
|
26
|
+
fail("Provider user ID cannot be empty")
|
|
27
|
+
|
|
28
|
+
client = get_client()
|
|
29
|
+
result = client.post("/auth/identity", {
|
|
30
|
+
"provider": provider,
|
|
31
|
+
"providerId": provider_id,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
output_json({
|
|
35
|
+
"success": result.get("success", True),
|
|
36
|
+
"provider": result.get("provider", provider),
|
|
37
|
+
"providerId": result.get("providerId", provider_id),
|
|
38
|
+
})
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Set label config on the relay API key.
|
|
3
|
+
|
|
4
|
+
Configures default labels and type-to-label mapping for automatic label
|
|
5
|
+
application during issue creation.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
set_labels.py --defaults '["app"]' --type-map '{"feature":["Feature"],...}'
|
|
9
|
+
echo '{"defaults":["app"],"typeMap":{...}}' | set_labels.py
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
18
|
+
from flydocs_api import get_client, output_json, fail
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main():
|
|
22
|
+
parser = argparse.ArgumentParser(description="Set label config on relay")
|
|
23
|
+
parser.add_argument("--defaults", default=None, help="JSON array of default label names")
|
|
24
|
+
parser.add_argument("--type-map", default=None, dest="type_map", help="JSON object mapping issue types to label arrays")
|
|
25
|
+
args = parser.parse_args()
|
|
26
|
+
|
|
27
|
+
# Build body from flags or stdin
|
|
28
|
+
if args.defaults is not None or args.type_map is not None:
|
|
29
|
+
body: dict = {}
|
|
30
|
+
if args.defaults is not None:
|
|
31
|
+
try:
|
|
32
|
+
body["defaults"] = json.loads(args.defaults)
|
|
33
|
+
except json.JSONDecodeError:
|
|
34
|
+
fail("Invalid JSON for --defaults")
|
|
35
|
+
if args.type_map is not None:
|
|
36
|
+
try:
|
|
37
|
+
body["typeMap"] = json.loads(args.type_map)
|
|
38
|
+
except json.JSONDecodeError:
|
|
39
|
+
fail("Invalid JSON for --type-map")
|
|
40
|
+
elif not sys.stdin.isatty():
|
|
41
|
+
try:
|
|
42
|
+
body = json.loads(sys.stdin.read().strip())
|
|
43
|
+
except json.JSONDecodeError:
|
|
44
|
+
fail("Invalid JSON on stdin")
|
|
45
|
+
else:
|
|
46
|
+
fail("Provide --defaults/--type-map flags or pipe JSON via stdin")
|
|
47
|
+
|
|
48
|
+
client = get_client()
|
|
49
|
+
result = client.post("/auth/labels", body)
|
|
50
|
+
|
|
51
|
+
# Store label config in local config as reference
|
|
52
|
+
config_path = client.config_path
|
|
53
|
+
if config_path.exists():
|
|
54
|
+
with open(config_path, "r") as f:
|
|
55
|
+
config = json.load(f)
|
|
56
|
+
config["labels"] = {
|
|
57
|
+
"defaults": body.get("defaults", []),
|
|
58
|
+
"typeMap": body.get("typeMap", {}),
|
|
59
|
+
}
|
|
60
|
+
with open(config_path, "w") as f:
|
|
61
|
+
json.dump(config, f, indent=2)
|
|
62
|
+
f.write("\n")
|
|
63
|
+
|
|
64
|
+
output_json(result)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
main()
|