@flydocs/cli 0.5.0-beta.9 → 0.6.0-alpha.2
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 +1259 -370
- package/package.json +1 -1
- 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 +109 -26
- package/template/.claude/commands/flydocs-upgrade.md +330 -0
- package/template/.claude/skills/flydocs-cloud/SKILL.md +53 -38
- package/template/.claude/skills/flydocs-cloud/cursor-rule.mdc +5 -5
- package/template/.claude/skills/flydocs-cloud/scripts/assign.py +8 -24
- 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 +21 -58
- 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/estimate.py +10 -19
- package/template/.claude/skills/flydocs-cloud/scripts/flydocs_api.py +103 -170
- 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_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/set_labels.py +68 -0
- package/template/.claude/skills/flydocs-cloud/scripts/set_team.py +41 -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 +23 -52
- package/template/.env.example +16 -7
- package/template/.flydocs/config.json +1 -1
- package/template/.flydocs/version +1 -1
- package/template/CHANGELOG.md +144 -0
- package/template/manifest.json +5 -3
|
@@ -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 teams 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,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()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Set team preference via the FlyDocs Relay API.
|
|
3
|
+
|
|
4
|
+
Stores the team preference on the relay (for server-side scoping)
|
|
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 team preference")
|
|
19
|
+
parser.add_argument("team_id", help="Linear team UUID")
|
|
20
|
+
args = parser.parse_args()
|
|
21
|
+
|
|
22
|
+
client = get_client()
|
|
23
|
+
result = client.post("/auth/team", {"teamId": args.team_id})
|
|
24
|
+
|
|
25
|
+
# Update local config with team ID
|
|
26
|
+
config_path = client.config_path
|
|
27
|
+
if config_path.exists():
|
|
28
|
+
with open(config_path, "r") as f:
|
|
29
|
+
config = json.load(f)
|
|
30
|
+
if "provider" not in config:
|
|
31
|
+
config["provider"] = {"type": "linear", "teamId": None}
|
|
32
|
+
config["provider"]["teamId"] = args.team_id
|
|
33
|
+
with open(config_path, "w") as f:
|
|
34
|
+
json.dump(config, f, indent=2)
|
|
35
|
+
f.write("\n")
|
|
36
|
+
|
|
37
|
+
output_json(result)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
main()
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""Transition an issue to a new status with a required comment."""
|
|
3
3
|
|
|
4
|
-
import json
|
|
5
4
|
import sys
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
|
|
@@ -14,54 +13,14 @@ if len(sys.argv) < 4:
|
|
|
14
13
|
ref, status, comment = sys.argv[1], sys.argv[2].upper(), sys.argv[3]
|
|
15
14
|
client = get_client()
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
""
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
)
|
|
29
|
-
prev_state = (current_result.get("data") or {}).get("issue", {}).get("state", {}).get("name", "Unknown")
|
|
30
|
-
|
|
31
|
-
# Resolve target state
|
|
32
|
-
state_id = client.get_state_id(status)
|
|
33
|
-
if not state_id:
|
|
34
|
-
fail(f"Unknown status: {status}. Check statusMapping in .flydocs/config.json")
|
|
35
|
-
|
|
36
|
-
# Build mutation input — combine state + cycle in one call
|
|
37
|
-
mutation_input: dict = {"stateId": state_id}
|
|
38
|
-
if status == "IMPLEMENTING":
|
|
39
|
-
cycle = client.get_active_cycle()
|
|
40
|
-
if cycle:
|
|
41
|
-
mutation_input["cycleId"] = cycle["id"]
|
|
42
|
-
|
|
43
|
-
# Update state (and cycle if IMPLEMENTING) in a single mutation
|
|
44
|
-
result = client.query(
|
|
45
|
-
"""mutation($id: String!, $input: IssueUpdateInput!) {
|
|
46
|
-
issueUpdate(id: $id, input: $input) {
|
|
47
|
-
success
|
|
48
|
-
issue { identifier state { name } }
|
|
49
|
-
}
|
|
50
|
-
}""",
|
|
51
|
-
{"id": issue_uuid, "input": mutation_input},
|
|
52
|
-
)
|
|
53
|
-
update_data = result.get("data", {}).get("issueUpdate", {})
|
|
54
|
-
if not update_data.get("success"):
|
|
55
|
-
fail(f"Failed to transition: {result}")
|
|
56
|
-
|
|
57
|
-
# Add comment
|
|
58
|
-
formatted = f"**{status}** — {comment}"
|
|
59
|
-
client.query(
|
|
60
|
-
"""mutation($id: String!, $body: String!) {
|
|
61
|
-
commentCreate(input: { issueId: $id, body: $body }) { success }
|
|
62
|
-
}""",
|
|
63
|
-
{"id": issue_uuid, "body": formatted},
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
issue = update_data["issue"]
|
|
67
|
-
output_json({"success": True, "issue": issue["identifier"], "previousStatus": prev_state, "newStatus": status})
|
|
16
|
+
result = client.post(f"/issues/{ref}/transition", {
|
|
17
|
+
"status": status,
|
|
18
|
+
"comment": comment,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
output_json({
|
|
22
|
+
"success": result.get("success", True),
|
|
23
|
+
"issue": result.get("issue", ref),
|
|
24
|
+
"previousStatus": result.get("previousStatus", ""),
|
|
25
|
+
"newStatus": result.get("newStatus", status),
|
|
26
|
+
})
|