@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,8 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: flydocs-cloud
|
|
3
3
|
description: |
|
|
4
|
-
Connected issue management via
|
|
5
|
-
Implements the FlyDocs mechanism contract with extended cloud
|
|
4
|
+
Connected issue management via FlyDocs Relay API.
|
|
5
|
+
Implements the FlyDocs mechanism contract with extended cloud operations.
|
|
6
|
+
All provider translation (Linear, Jira) happens server-side.
|
|
6
7
|
triggers:
|
|
7
8
|
- create issue
|
|
8
9
|
- transition
|
|
@@ -18,7 +19,9 @@ triggers:
|
|
|
18
19
|
|
|
19
20
|
# FlyDocs Cloud Mechanism
|
|
20
21
|
|
|
21
|
-
Issues managed
|
|
22
|
+
Issues managed via the FlyDocs Relay API. The relay handles provider translation server-side — scripts are thin REST wrappers.
|
|
23
|
+
|
|
24
|
+
Reads config from `.flydocs/config.json` and API key (`FLYDOCS_API_KEY`) from `.env`.
|
|
22
25
|
|
|
23
26
|
## Script Catalog
|
|
24
27
|
|
|
@@ -26,42 +29,51 @@ All scripts: `python3 .claude/skills/flydocs-cloud/scripts/<script>`
|
|
|
26
29
|
|
|
27
30
|
### Shared Contract Scripts
|
|
28
31
|
|
|
29
|
-
| Script
|
|
30
|
-
|
|
31
|
-
| `create_issue.py`
|
|
32
|
-
| `transition.py`
|
|
33
|
-
| `comment.py`
|
|
34
|
-
| `list_issues.py`
|
|
35
|
-
| `get_issue.py`
|
|
36
|
-
| `assign.py`
|
|
37
|
-
| `update_description.py` | `<ref> --text "..." \| --file PATH \| stdin`
|
|
38
|
-
|
|
39
|
-
### Extended Scripts
|
|
40
|
-
|
|
41
|
-
| Script
|
|
42
|
-
|
|
43
|
-
| `update_issue.py`
|
|
44
|
-
| `estimate.py`
|
|
45
|
-
| `priority.py`
|
|
46
|
-
| `link.py`
|
|
47
|
-
| `project_update.py`
|
|
48
|
-
| `list_projects.py`
|
|
49
|
-
| `create_project.py`
|
|
50
|
-
| `assign_cycle.py`
|
|
51
|
-
| `list_cycles.py`
|
|
52
|
-
| `list_milestones.py`
|
|
53
|
-
| `create_milestone.py` | `--name "..." [--project ID] [--target-date DATE]`
|
|
54
|
-
| `assign_milestone.py` | `<ref> <milestone_id>`
|
|
32
|
+
| Script | Usage | Output |
|
|
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>"` | `{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
|
+
|
|
42
|
+
### Extended Scripts
|
|
43
|
+
|
|
44
|
+
| Script | Usage | Output |
|
|
45
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ |
|
|
46
|
+
| `update_issue.py` | `<ref> [--title "..."] [--priority 0-4] [--estimate 1-5] [--assignee STR] [--state STATUS] [--description "..."] [--description-file PATH] [--labels "a,b"] [--comment "..."]` | `{success, issue, updated[]}` |
|
|
47
|
+
| `estimate.py` | `<ref> <1-5>` | `{success, issue, estimate}` |
|
|
48
|
+
| `priority.py` | `<ref> <0-4>` | `{success, issue, priority}` |
|
|
49
|
+
| `link.py` | `<ref> <related_ref> <type>` | `{success, type}` |
|
|
50
|
+
| `project_update.py` | `--health STATUS --body "..." [--body-file PATH]` | `{success, id}` |
|
|
51
|
+
| `list_projects.py` | `[--active] [--all]` | `[{id, name, state}]` — `--all` bypasses product scope |
|
|
52
|
+
| `create_project.py` | `--name "..." [--description "..."]` | `{id, name, url}` |
|
|
53
|
+
| `assign_cycle.py` | `<ref> [cycle_id]` | `{success, issue, cycle}` |
|
|
54
|
+
| `list_cycles.py` | `[--active]` | `[{id, name, number, startsAt, endsAt}]` |
|
|
55
|
+
| `list_milestones.py` | `[--all]` | `[{id, name, targetDate}]` |
|
|
56
|
+
| `create_milestone.py` | `--name "..." [--project ID] [--target-date DATE]` | `{id, name}` |
|
|
57
|
+
| `assign_milestone.py` | `<ref> <milestone_id>` | `{success, issue, milestone}` |
|
|
58
|
+
|
|
59
|
+
### Workspace Scripts
|
|
60
|
+
|
|
61
|
+
| Script | Usage | Output |
|
|
62
|
+
| ---------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------- |
|
|
63
|
+
| `list_teams.py` | (no args) | `[{id, name, key}]` |
|
|
64
|
+
| `create_team.py` | `--name "..." [--key KEY] [--description "..."] [--parent <team_id>]` | `{id, name, key}` — `--parent` creates a sub-team |
|
|
65
|
+
| `set_team.py` | `<team_id>` | `{success}` — updates relay preference and local config `provider.teamId` |
|
|
66
|
+
| `list_labels.py` | (no args) | `[{id, name, color}]` — requires team to be set first |
|
|
67
|
+
| `set_labels.py` | `--defaults '["a"]' --type-map '{"feature":["F"],...}' \| stdin` | `{success, validated, defaults, typeMap}` — stores label config on relay |
|
|
55
68
|
|
|
56
69
|
### Script Notes
|
|
57
70
|
|
|
58
|
-
-
|
|
59
|
-
- **`list_issues.py --active`**: Returns all non-terminal issues (excludes Done, Archived, Canceled, Duplicate).
|
|
60
|
-
- **`list_issues.py --milestone`**: Filter issues by project milestone ID. Get IDs from `list_milestones.py`.
|
|
71
|
+
- **How it works**: Scripts call the FlyDocs Relay REST API, which translates to the provider (Linear, Jira) server-side. Same interface and output as before — the transport changed from direct GraphQL to managed REST.
|
|
72
|
+
- **`list_issues.py --active`**: Returns all non-terminal issues (excludes Done, Archived, Canceled, Duplicate).
|
|
61
73
|
- **`list_issues.py --status`**: Accepts comma-separated statuses: `--status READY,IMPLEMENTING,BLOCKED`
|
|
62
|
-
- **`get_issue.py --fields basic`**: Skips comment fetch for faster responses
|
|
63
|
-
- **`update_issue.py`**: Bulk update — sets multiple fields in a single API call. Prefer over separate
|
|
64
|
-
- **Shell-safe text input**: For
|
|
74
|
+
- **`get_issue.py --fields basic`**: Skips comment fetch for faster responses.
|
|
75
|
+
- **`update_issue.py`**: Bulk update — sets multiple fields in a single API call. Prefer over separate scripts when updating more than one field.
|
|
76
|
+
- **Shell-safe text input**: For text with special characters, pipe via stdin with a single-quoted heredoc:
|
|
65
77
|
```bash
|
|
66
78
|
python3 update_description.py ENG-123 <<'EOF'
|
|
67
79
|
Description with 'apostrophes', (parens), and "quotes"
|
|
@@ -83,14 +95,17 @@ All scripts: `python3 .claude/skills/flydocs-cloud/scripts/<script>`
|
|
|
83
95
|
|
|
84
96
|
### Issue Reference
|
|
85
97
|
|
|
86
|
-
`<ref>`: Provider identifier, e.g., `ENG-123`. Resolved
|
|
98
|
+
`<ref>`: Provider identifier, e.g., `ENG-123`. Resolved server-side by the relay.
|
|
87
99
|
|
|
88
100
|
## Error Handling
|
|
89
101
|
|
|
90
102
|
Exit 0 = success (JSON on stdout). Exit 1 = error (message on stderr).
|
|
91
103
|
Network errors: exponential backoff, 3 retries, 2s base delay.
|
|
104
|
+
Relay errors include `code` and optional `provider_error` for debugging.
|
|
92
105
|
|
|
93
106
|
## Configuration
|
|
94
107
|
|
|
95
|
-
Reads from `.flydocs/config.json`: tier,
|
|
96
|
-
Reads `
|
|
108
|
+
Reads from `.flydocs/config.json`: tier, relay URL override.
|
|
109
|
+
Reads `FLYDOCS_API_KEY` from environment or `.env` / `.env.local`.
|
|
110
|
+
|
|
111
|
+
Optional: `FLYDOCS_RELAY_URL` env var or `relay.url` in config to override the base URL (e.g., for local development).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Connected issue management via
|
|
2
|
+
description: Connected issue management via FlyDocs Relay API — cloud mechanism for FlyDocs
|
|
3
3
|
alwaysApply: true
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -7,7 +7,7 @@ alwaysApply: true
|
|
|
7
7
|
|
|
8
8
|
# FlyDocs Cloud Mechanism
|
|
9
9
|
|
|
10
|
-
Issues managed
|
|
10
|
+
Issues managed via FlyDocs Relay API. Reads config from `.flydocs/config.json` and API key (`FLYDOCS_API_KEY`) from `.env`.
|
|
11
11
|
|
|
12
12
|
## Shared Contract Scripts
|
|
13
13
|
|
|
@@ -23,7 +23,7 @@ All scripts: `python3 .claude/skills/flydocs-cloud/scripts/<script>`
|
|
|
23
23
|
| `assign.py` | `<ref> <assignee>` |
|
|
24
24
|
| `update_description.py` | `<ref> --text "..." \| --file PATH` |
|
|
25
25
|
|
|
26
|
-
## Extended Scripts
|
|
26
|
+
## Extended Scripts
|
|
27
27
|
|
|
28
28
|
| Script | Key Arguments |
|
|
29
29
|
|--------|---------------|
|
|
@@ -46,5 +46,5 @@ Network errors: exponential backoff, 3 retries, 2s base delay.
|
|
|
46
46
|
|
|
47
47
|
## Configuration
|
|
48
48
|
|
|
49
|
-
Reads from `.flydocs/config.json`: tier,
|
|
50
|
-
Reads `
|
|
49
|
+
Reads from `.flydocs/config.json`: tier, relay URL override.
|
|
50
|
+
Reads `FLYDOCS_API_KEY` from environment or `.env` / `.env.local`.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Assign an issue to a
|
|
2
|
+
"""Assign an issue to a user via the FlyDocs Relay API."""
|
|
3
3
|
|
|
4
4
|
import sys
|
|
5
5
|
from pathlib import Path
|
|
@@ -10,29 +10,13 @@ from flydocs_api import get_client, output_json, fail
|
|
|
10
10
|
if len(sys.argv) < 3:
|
|
11
11
|
fail("Usage: assign.py <ref> <assignee>")
|
|
12
12
|
|
|
13
|
-
ref,
|
|
13
|
+
ref, assignee = sys.argv[1], sys.argv[2]
|
|
14
14
|
client = get_client()
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
if not issue_uuid:
|
|
18
|
-
fail(f"Issue not found: {ref}")
|
|
16
|
+
result = client.post(f"/issues/{ref}/assign", {"assignee": assignee})
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"""mutation($id: String!, $assigneeId: String!) {
|
|
26
|
-
issueUpdate(id: $id, input: { assigneeId: $assigneeId }) {
|
|
27
|
-
success
|
|
28
|
-
issue { identifier }
|
|
29
|
-
}
|
|
30
|
-
}""",
|
|
31
|
-
{"id": issue_uuid, "assigneeId": user_id},
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
if not result.get("data", {}).get("issueUpdate", {}).get("success"):
|
|
35
|
-
fail(f"Failed to assign: {result}")
|
|
36
|
-
|
|
37
|
-
issue = result["data"]["issueUpdate"]["issue"]
|
|
38
|
-
output_json({"success": True, "issue": issue["identifier"], "assignee": user_name})
|
|
18
|
+
output_json({
|
|
19
|
+
"success": result.get("success", True),
|
|
20
|
+
"issue": result.get("issue", ref),
|
|
21
|
+
"assignee": result.get("assignee", assignee),
|
|
22
|
+
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Assign an issue
|
|
2
|
+
"""Assign a cycle to an issue via the FlyDocs Relay API."""
|
|
3
3
|
|
|
4
4
|
import sys
|
|
5
5
|
from pathlib import Path
|
|
@@ -11,34 +11,18 @@ if len(sys.argv) < 2:
|
|
|
11
11
|
fail("Usage: assign_cycle.py <ref> [cycle_id]")
|
|
12
12
|
|
|
13
13
|
ref = sys.argv[1]
|
|
14
|
+
cycle_id = sys.argv[2] if len(sys.argv) >= 3 else None
|
|
15
|
+
|
|
14
16
|
client = get_client()
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
if
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
cycle_id = cycle["id"]
|
|
28
|
-
cycle_name = cycle["name"]
|
|
29
|
-
|
|
30
|
-
result = client.query(
|
|
31
|
-
"""mutation($id: String!, $cycleId: String!) {
|
|
32
|
-
issueUpdate(id: $id, input: { cycleId: $cycleId }) {
|
|
33
|
-
success
|
|
34
|
-
issue { identifier }
|
|
35
|
-
}
|
|
36
|
-
}""",
|
|
37
|
-
{"id": issue_uuid, "cycleId": cycle_id},
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
if not result.get("data", {}).get("issueUpdate", {}).get("success"):
|
|
41
|
-
fail(f"Failed to assign cycle: {result}")
|
|
42
|
-
|
|
43
|
-
issue = result["data"]["issueUpdate"]["issue"]
|
|
44
|
-
output_json({"success": True, "issue": issue["identifier"], "cycle": cycle_name})
|
|
18
|
+
body: dict = {}
|
|
19
|
+
if cycle_id:
|
|
20
|
+
body["cycleId"] = cycle_id
|
|
21
|
+
|
|
22
|
+
result = client.put(f"/issues/{ref}/cycle", body)
|
|
23
|
+
|
|
24
|
+
output_json({
|
|
25
|
+
"success": result.get("success", True),
|
|
26
|
+
"issue": result.get("issue", ref),
|
|
27
|
+
"cycle": result.get("cycle", ""),
|
|
28
|
+
})
|
|
@@ -1,44 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Assign an issue
|
|
2
|
+
"""Assign a milestone to an issue via the FlyDocs Relay API."""
|
|
3
3
|
|
|
4
|
-
import argparse
|
|
5
4
|
import sys
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
|
|
8
7
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
9
8
|
from flydocs_api import get_client, output_json, fail
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
parser.add_argument("milestone_id", nargs="?", default=None,
|
|
14
|
-
help="Milestone UUID (positional)")
|
|
15
|
-
parser.add_argument("--milestone", dest="milestone_flag", default=None,
|
|
16
|
-
help="Milestone UUID (flag)")
|
|
17
|
-
args = parser.parse_args()
|
|
18
|
-
|
|
19
|
-
milestone_id = args.milestone_flag or args.milestone_id
|
|
20
|
-
if not milestone_id:
|
|
21
|
-
fail("Milestone ID required. Usage: assign_milestone.py <ref> <milestone_id> or --milestone <id>")
|
|
10
|
+
if len(sys.argv) < 3:
|
|
11
|
+
fail("Usage: assign_milestone.py <ref> <milestone_id>")
|
|
22
12
|
|
|
13
|
+
ref, milestone_id = sys.argv[1], sys.argv[2]
|
|
23
14
|
client = get_client()
|
|
24
15
|
|
|
25
|
-
|
|
26
|
-
if not issue_uuid:
|
|
27
|
-
fail(f"Issue not found: {args.ref}")
|
|
28
|
-
|
|
29
|
-
result = client.query(
|
|
30
|
-
"""mutation($id: String!, $milestoneId: String!) {
|
|
31
|
-
issueUpdate(id: $id, input: { projectMilestoneId: $milestoneId }) {
|
|
32
|
-
success
|
|
33
|
-
issue { identifier }
|
|
34
|
-
}
|
|
35
|
-
}""",
|
|
36
|
-
{"id": issue_uuid, "milestoneId": milestone_id},
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
update = result.get("data", {}).get("issueUpdate", {})
|
|
40
|
-
if not update.get("success"):
|
|
41
|
-
fail(f"Failed to assign milestone: {result}")
|
|
16
|
+
result = client.put(f"/issues/{ref}/milestone", {"milestoneId": milestone_id})
|
|
42
17
|
|
|
43
|
-
|
|
44
|
-
|
|
18
|
+
output_json({
|
|
19
|
+
"success": result.get("success", True),
|
|
20
|
+
"issue": result.get("issue", ref),
|
|
21
|
+
"milestone": result.get("milestone", ""),
|
|
22
|
+
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Add a comment to an issue."""
|
|
2
|
+
"""Add a comment to an issue via the FlyDocs Relay API."""
|
|
3
3
|
|
|
4
4
|
import sys
|
|
5
5
|
from pathlib import Path
|
|
@@ -8,32 +8,22 @@ sys.path.insert(0, str(Path(__file__).parent))
|
|
|
8
8
|
from flydocs_api import get_client, output_json, fail
|
|
9
9
|
|
|
10
10
|
if len(sys.argv) < 2:
|
|
11
|
-
fail("Usage: comment.py <ref> [
|
|
11
|
+
fail("Usage: comment.py <ref> [comment] | stdin")
|
|
12
12
|
|
|
13
13
|
ref = sys.argv[1]
|
|
14
|
-
body = sys.argv[2] if len(sys.argv) > 2 else ""
|
|
15
|
-
if not body and not sys.stdin.isatty():
|
|
16
|
-
body = sys.stdin.read().strip()
|
|
17
|
-
if not body:
|
|
18
|
-
fail("Provide comment as argument or pipe via stdin")
|
|
19
|
-
client = get_client()
|
|
20
|
-
|
|
21
|
-
issue_uuid = client.resolve_issue_id(ref)
|
|
22
|
-
if not issue_uuid:
|
|
23
|
-
fail(f"Issue not found: {ref}")
|
|
24
14
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
{"id": issue_uuid, "body": body},
|
|
33
|
-
)
|
|
15
|
+
# Comment from arg > stdin
|
|
16
|
+
if len(sys.argv) >= 3:
|
|
17
|
+
body = sys.argv[2]
|
|
18
|
+
elif not sys.stdin.isatty():
|
|
19
|
+
body = sys.stdin.read().strip()
|
|
20
|
+
else:
|
|
21
|
+
fail("Provide comment as argument or via stdin")
|
|
34
22
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
fail(f"Failed to add comment: {result}")
|
|
23
|
+
client = get_client()
|
|
24
|
+
result = client.post(f"/issues/{ref}/comment", {"body": body})
|
|
38
25
|
|
|
39
|
-
output_json({
|
|
26
|
+
output_json({
|
|
27
|
+
"success": result.get("success", True),
|
|
28
|
+
"commentId": result.get("commentId", ""),
|
|
29
|
+
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Create a new issue
|
|
2
|
+
"""Create a new issue via the FlyDocs Relay API."""
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
5
|
import sys
|
|
@@ -18,7 +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("--
|
|
21
|
+
parser.add_argument("--labels", default=None, help="Comma-separated ad-hoc label names")
|
|
22
22
|
parser.add_argument("--triage", action="store_true")
|
|
23
23
|
args = parser.parse_args()
|
|
24
24
|
|
|
@@ -32,68 +32,31 @@ def main():
|
|
|
32
32
|
elif not description and not sys.stdin.isatty():
|
|
33
33
|
description = sys.stdin.read().strip()
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
issue_input = {
|
|
38
|
-
"teamId": client.team_id,
|
|
35
|
+
body: dict = {
|
|
39
36
|
"title": args.title,
|
|
40
|
-
"
|
|
37
|
+
"type": args.issue_type,
|
|
41
38
|
"priority": args.priority,
|
|
42
39
|
}
|
|
40
|
+
if description:
|
|
41
|
+
body["description"] = description
|
|
43
42
|
if args.estimate:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# Labels
|
|
47
|
-
label_ids = []
|
|
48
|
-
cat_id = client.get_category_label_id(args.issue_type)
|
|
49
|
-
if cat_id:
|
|
50
|
-
label_ids.append(cat_id)
|
|
51
|
-
if args.triage:
|
|
52
|
-
triage_id = client.get_other_label_id("triage")
|
|
53
|
-
if triage_id:
|
|
54
|
-
label_ids.append(triage_id)
|
|
55
|
-
if label_ids:
|
|
56
|
-
issue_input["labelIds"] = label_ids
|
|
57
|
-
|
|
58
|
-
# Project
|
|
59
|
-
project_id = args.project
|
|
60
|
-
if not project_id:
|
|
61
|
-
active = client.workspace.get("activeProjects", [])
|
|
62
|
-
if active:
|
|
63
|
-
project_id = active[0]
|
|
64
|
-
if project_id:
|
|
65
|
-
issue_input["projectId"] = project_id
|
|
66
|
-
|
|
67
|
-
# Assignee
|
|
43
|
+
body["estimate"] = args.estimate
|
|
68
44
|
if args.assignee:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
issueCreate(input: $input) {
|
|
75
|
-
success
|
|
76
|
-
issue { id identifier title url }
|
|
77
|
-
}
|
|
78
|
-
}"""
|
|
79
|
-
|
|
80
|
-
result = client.query(mutation, {"input": issue_input})
|
|
81
|
-
|
|
82
|
-
data = result.get("data", {}).get("issueCreate", {})
|
|
83
|
-
if not data.get("success"):
|
|
84
|
-
# Retry without labels if team mismatch (stale label IDs in config)
|
|
85
|
-
errors = result.get("errors", [])
|
|
86
|
-
label_error = any("label" in (e.get("message", "") or "").lower() for e in errors)
|
|
87
|
-
if label_error and "labelIds" in issue_input:
|
|
88
|
-
print("Label IDs rejected — retrying without labels...", file=sys.stderr)
|
|
89
|
-
del issue_input["labelIds"]
|
|
90
|
-
result = client.query(mutation, {"input": issue_input})
|
|
91
|
-
data = result.get("data", {}).get("issueCreate", {})
|
|
92
|
-
if not data.get("success"):
|
|
93
|
-
fail(f"Failed to create issue: {result}")
|
|
45
|
+
body["assignee"] = args.assignee
|
|
46
|
+
if args.labels:
|
|
47
|
+
body["labels"] = [l.strip() for l in args.labels.split(",") if l.strip()]
|
|
48
|
+
if args.triage:
|
|
49
|
+
body["triage"] = True
|
|
94
50
|
|
|
95
|
-
|
|
96
|
-
|
|
51
|
+
client = get_client()
|
|
52
|
+
result = client.post("/issues", body)
|
|
53
|
+
|
|
54
|
+
output_json({
|
|
55
|
+
"id": result["id"],
|
|
56
|
+
"identifier": result["identifier"],
|
|
57
|
+
"title": result["title"],
|
|
58
|
+
"url": result["url"],
|
|
59
|
+
})
|
|
97
60
|
|
|
98
61
|
|
|
99
62
|
if __name__ == "__main__":
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Create a milestone."""
|
|
2
|
+
"""Create a milestone via the FlyDocs Relay API."""
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
5
|
import sys
|
|
@@ -8,39 +8,28 @@ 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
|
|
14
|
-
parser.add_argument("--
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if
|
|
21
|
-
|
|
22
|
-
if
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
}""",
|
|
38
|
-
{"input": milestone_input},
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
data = result.get("data", {}).get("projectMilestoneCreate", {})
|
|
42
|
-
if not data.get("success"):
|
|
43
|
-
fail(f"Failed to create milestone: {result}")
|
|
44
|
-
|
|
45
|
-
ms = data["projectMilestone"]
|
|
46
|
-
output_json({"id": ms["id"], "name": ms["name"]})
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
parser = argparse.ArgumentParser(description="Create milestone")
|
|
14
|
+
parser.add_argument("--name", required=True)
|
|
15
|
+
parser.add_argument("--project", default=None, help="Project ID")
|
|
16
|
+
parser.add_argument("--target-date", default=None, dest="target_date")
|
|
17
|
+
args = parser.parse_args()
|
|
18
|
+
|
|
19
|
+
body: dict = {"name": args.name}
|
|
20
|
+
if args.project:
|
|
21
|
+
body["projectId"] = args.project
|
|
22
|
+
if args.target_date:
|
|
23
|
+
body["targetDate"] = args.target_date
|
|
24
|
+
|
|
25
|
+
client = get_client()
|
|
26
|
+
result = client.post("/milestones", body)
|
|
27
|
+
|
|
28
|
+
output_json({
|
|
29
|
+
"id": result["id"],
|
|
30
|
+
"name": result["name"],
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if __name__ == "__main__":
|
|
35
|
+
main()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Create a
|
|
2
|
+
"""Create a project via the FlyDocs Relay API."""
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
5
|
import sys
|
|
@@ -8,33 +8,26 @@ 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.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
data = result.get("data", {}).get("projectCreate", {})
|
|
36
|
-
if not data.get("success"):
|
|
37
|
-
fail(f"Failed to create project: {result}")
|
|
38
|
-
|
|
39
|
-
project = data["project"]
|
|
40
|
-
output_json({"id": project["id"], "name": project["name"], "url": project["url"]})
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
parser = argparse.ArgumentParser(description="Create project")
|
|
14
|
+
parser.add_argument("--name", required=True)
|
|
15
|
+
parser.add_argument("--description", default=None)
|
|
16
|
+
args = parser.parse_args()
|
|
17
|
+
|
|
18
|
+
body: dict = {"name": args.name}
|
|
19
|
+
if args.description:
|
|
20
|
+
body["description"] = args.description
|
|
21
|
+
|
|
22
|
+
client = get_client()
|
|
23
|
+
result = client.post("/projects", body)
|
|
24
|
+
|
|
25
|
+
output_json({
|
|
26
|
+
"id": result["id"],
|
|
27
|
+
"name": result["name"],
|
|
28
|
+
"url": result.get("url", ""),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
main()
|