@flydocs/cli 0.6.0-alpha.7 → 0.6.0-alpha.9
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 +88 -16
- package/package.json +1 -1
- package/template/.claude/skills/flydocs-cloud/SKILL.md +2 -1
- package/template/.claude/skills/flydocs-cloud/scripts/assign.py +10 -4
- package/template/.claude/skills/flydocs-cloud/scripts/flydocs_api.py +7 -0
- package/template/.claude/skills/flydocs-cloud/scripts/validate_setup.py +134 -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/.flydocs/config.json +4 -18
- package/template/.flydocs/hooks/prompt-submit.py +21 -4
- package/template/.flydocs/version +1 -1
- package/template/manifest.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -15,7 +15,7 @@ var CLI_VERSION, CLI_NAME, PACKAGE_NAME, POSTHOG_API_KEY;
|
|
|
15
15
|
var init_constants = __esm({
|
|
16
16
|
"src/lib/constants.ts"() {
|
|
17
17
|
"use strict";
|
|
18
|
-
CLI_VERSION = "0.6.0-alpha.
|
|
18
|
+
CLI_VERSION = "0.6.0-alpha.9";
|
|
19
19
|
CLI_NAME = "flydocs";
|
|
20
20
|
PACKAGE_NAME = "@flydocs/cli";
|
|
21
21
|
POSTHOG_API_KEY = "phc_v1MSJTQDFkMS90CBh3mxIz3v8bYCCnKU6v1ir6bz0Xn";
|
|
@@ -293,10 +293,9 @@ function extractPreservedValues(config) {
|
|
|
293
293
|
return {
|
|
294
294
|
tier: config.tier,
|
|
295
295
|
setupComplete: config.setupComplete ?? false,
|
|
296
|
-
|
|
296
|
+
workspaceId: config.workspaceId ?? null,
|
|
297
297
|
workspace: config.workspace ?? {},
|
|
298
298
|
issueLabels: config.issueLabels ?? {},
|
|
299
|
-
statusMapping: config.statusMapping ?? {},
|
|
300
299
|
detectedStack: config.detectedStack ?? {},
|
|
301
300
|
skills: config.skills ?? {},
|
|
302
301
|
designSystem: config.designSystem ?? null,
|
|
@@ -312,21 +311,13 @@ async function mergeConfig(templateDir, version, tierFlag, preserved) {
|
|
|
312
311
|
config.version = version;
|
|
313
312
|
config.tier = tierFlag ?? preserved.tier;
|
|
314
313
|
config.setupComplete = preserved.setupComplete;
|
|
315
|
-
|
|
316
|
-
if (!config.provider) {
|
|
317
|
-
config.provider = { type: "linear", teamId: null };
|
|
318
|
-
}
|
|
319
|
-
config.provider.teamId = preserved.providerTeamId;
|
|
320
|
-
}
|
|
314
|
+
config.workspaceId = preserved.workspaceId;
|
|
321
315
|
if (Object.keys(preserved.workspace).length > 0) {
|
|
322
316
|
config.workspace = preserved.workspace;
|
|
323
317
|
}
|
|
324
318
|
if (Object.keys(preserved.issueLabels).length > 0) {
|
|
325
319
|
config.issueLabels = preserved.issueLabels;
|
|
326
320
|
}
|
|
327
|
-
if (Object.keys(preserved.statusMapping).length > 0) {
|
|
328
|
-
config.statusMapping = preserved.statusMapping;
|
|
329
|
-
}
|
|
330
321
|
if (Object.keys(preserved.detectedStack).length > 0) {
|
|
331
322
|
config.detectedStack = preserved.detectedStack;
|
|
332
323
|
}
|
|
@@ -1888,6 +1879,21 @@ async function validateRelayKey(apiKey) {
|
|
|
1888
1879
|
if (!data.valid) return { valid: false };
|
|
1889
1880
|
return { valid: true, org: data.org ?? "your organization" };
|
|
1890
1881
|
}
|
|
1882
|
+
async function fetchWorkspaces(apiKey) {
|
|
1883
|
+
const baseUrl = process.env.FLYDOCS_RELAY_URL?.replace(/\/$/, "") ?? "https://app.flydocs.ai/api/relay";
|
|
1884
|
+
const response = await fetch(`${baseUrl}/auth/workspaces`, {
|
|
1885
|
+
method: "GET",
|
|
1886
|
+
headers: {
|
|
1887
|
+
Authorization: `Bearer ${apiKey}`
|
|
1888
|
+
},
|
|
1889
|
+
signal: AbortSignal.timeout(15e3)
|
|
1890
|
+
});
|
|
1891
|
+
if (!response.ok) {
|
|
1892
|
+
throw new Error(`Failed to fetch workspaces: ${response.status}`);
|
|
1893
|
+
}
|
|
1894
|
+
const data = await response.json();
|
|
1895
|
+
return data;
|
|
1896
|
+
}
|
|
1891
1897
|
async function validateLinearKey(apiKey) {
|
|
1892
1898
|
const response = await fetch("https://api.linear.app/graphql", {
|
|
1893
1899
|
method: "POST",
|
|
@@ -2074,6 +2080,8 @@ var init_install = __esm({
|
|
|
2074
2080
|
}
|
|
2075
2081
|
printInfo(`Tier: ${tier}`);
|
|
2076
2082
|
await capture("install_tier_selected", { tier });
|
|
2083
|
+
let selectedWorkspaceId = null;
|
|
2084
|
+
let selectedWorkspaceName = null;
|
|
2077
2085
|
if (tier === "cloud") {
|
|
2078
2086
|
console.log();
|
|
2079
2087
|
console.log(` ${pc6.bold("Connect to FlyDocs Cloud")}`);
|
|
@@ -2115,6 +2123,46 @@ var init_install = __esm({
|
|
|
2115
2123
|
}
|
|
2116
2124
|
const envFile = await storeEnvKey(targetDir, "FLYDOCS_API_KEY", apiKey);
|
|
2117
2125
|
printStatus(`API key stored in ${pc6.dim(envFile)}`);
|
|
2126
|
+
console.log();
|
|
2127
|
+
printInfo("Fetching workspaces...");
|
|
2128
|
+
try {
|
|
2129
|
+
const workspaces = await fetchWorkspaces(apiKey);
|
|
2130
|
+
if (workspaces.length === 0) {
|
|
2131
|
+
printError(
|
|
2132
|
+
"No workspaces found. Create a workspace in the FlyDocs dashboard first, then re-run install."
|
|
2133
|
+
);
|
|
2134
|
+
process.exit(1);
|
|
2135
|
+
}
|
|
2136
|
+
if (workspaces.length === 1) {
|
|
2137
|
+
selectedWorkspaceId = workspaces[0].id;
|
|
2138
|
+
selectedWorkspaceName = workspaces[0].name;
|
|
2139
|
+
printStatus(
|
|
2140
|
+
`Workspace: ${pc6.bold(selectedWorkspaceName)} (auto-selected)`
|
|
2141
|
+
);
|
|
2142
|
+
} else {
|
|
2143
|
+
const workspaceChoice = await select({
|
|
2144
|
+
message: "Select a workspace",
|
|
2145
|
+
options: workspaces.map((ws) => ({
|
|
2146
|
+
value: ws.id,
|
|
2147
|
+
label: `${ws.name} (${ws.provider.type})`,
|
|
2148
|
+
hint: ws.team.name
|
|
2149
|
+
}))
|
|
2150
|
+
});
|
|
2151
|
+
if (isCancel3(workspaceChoice)) {
|
|
2152
|
+
cancel2("Installation cancelled.");
|
|
2153
|
+
process.exit(0);
|
|
2154
|
+
}
|
|
2155
|
+
selectedWorkspaceId = workspaceChoice;
|
|
2156
|
+
const chosen = workspaces.find((ws) => ws.id === selectedWorkspaceId);
|
|
2157
|
+
selectedWorkspaceName = chosen?.name ?? "Unknown";
|
|
2158
|
+
printStatus(`Workspace: ${pc6.bold(selectedWorkspaceName)}`);
|
|
2159
|
+
}
|
|
2160
|
+
} catch {
|
|
2161
|
+
printError(
|
|
2162
|
+
"Could not fetch workspaces. Check your network and try again."
|
|
2163
|
+
);
|
|
2164
|
+
process.exit(1);
|
|
2165
|
+
}
|
|
2118
2166
|
}
|
|
2119
2167
|
let skipConfigOverwrite = false;
|
|
2120
2168
|
if (!await pathExists(join15(targetDir, ".git"))) {
|
|
@@ -2196,6 +2244,9 @@ var init_install = __esm({
|
|
|
2196
2244
|
const configPath = join15(targetDir, ".flydocs", "config.json");
|
|
2197
2245
|
if (!await pathExists(configPath)) {
|
|
2198
2246
|
const config = await createFreshConfig(templateDir, version, tier);
|
|
2247
|
+
if (selectedWorkspaceId) {
|
|
2248
|
+
config.workspaceId = selectedWorkspaceId;
|
|
2249
|
+
}
|
|
2199
2250
|
await writeConfig(targetDir, config);
|
|
2200
2251
|
printStatus(`.flydocs/config.json (new, tier: ${tier})`);
|
|
2201
2252
|
} else if (args.tier) {
|
|
@@ -2203,15 +2254,34 @@ var init_install = __esm({
|
|
|
2203
2254
|
const existing = await readConfig(targetDir);
|
|
2204
2255
|
existing.version = version;
|
|
2205
2256
|
existing.tier = tier;
|
|
2257
|
+
if (selectedWorkspaceId) {
|
|
2258
|
+
existing.workspaceId = selectedWorkspaceId;
|
|
2259
|
+
}
|
|
2206
2260
|
await writeConfig(targetDir, existing);
|
|
2207
2261
|
printStatus(`.flydocs/config.json (tier updated: ${tier})`);
|
|
2208
2262
|
} catch {
|
|
2209
2263
|
const config = await createFreshConfig(templateDir, version, tier);
|
|
2264
|
+
if (selectedWorkspaceId) {
|
|
2265
|
+
config.workspaceId = selectedWorkspaceId;
|
|
2266
|
+
}
|
|
2210
2267
|
await writeConfig(targetDir, config);
|
|
2211
2268
|
printStatus(`.flydocs/config.json (recreated, tier: ${tier})`);
|
|
2212
2269
|
}
|
|
2213
2270
|
} else {
|
|
2214
|
-
|
|
2271
|
+
if (selectedWorkspaceId) {
|
|
2272
|
+
try {
|
|
2273
|
+
const existing = await readConfig(targetDir);
|
|
2274
|
+
existing.workspaceId = selectedWorkspaceId;
|
|
2275
|
+
await writeConfig(targetDir, existing);
|
|
2276
|
+
printWarning(
|
|
2277
|
+
".flydocs/config.json exists, preserving (workspace updated)"
|
|
2278
|
+
);
|
|
2279
|
+
} catch {
|
|
2280
|
+
printWarning(".flydocs/config.json exists, preserving");
|
|
2281
|
+
}
|
|
2282
|
+
} else {
|
|
2283
|
+
printWarning(".flydocs/config.json exists, preserving");
|
|
2284
|
+
}
|
|
2215
2285
|
}
|
|
2216
2286
|
console.log();
|
|
2217
2287
|
console.log(`Installing skills (tier: ${tier})...`);
|
|
@@ -2810,10 +2880,9 @@ var init_update = __esm({
|
|
|
2810
2880
|
let preserved = {
|
|
2811
2881
|
tier: "cloud",
|
|
2812
2882
|
setupComplete: false,
|
|
2813
|
-
|
|
2883
|
+
workspaceId: null,
|
|
2814
2884
|
workspace: {},
|
|
2815
2885
|
issueLabels: {},
|
|
2816
|
-
statusMapping: {},
|
|
2817
2886
|
detectedStack: {},
|
|
2818
2887
|
skills: {},
|
|
2819
2888
|
designSystem: null,
|
|
@@ -2837,6 +2906,7 @@ var init_update = __esm({
|
|
|
2837
2906
|
} else {
|
|
2838
2907
|
effectiveTier = preserved.tier;
|
|
2839
2908
|
}
|
|
2909
|
+
await ensureDirectories(targetDir, effectiveTier);
|
|
2840
2910
|
console.log("Replacing framework directories...");
|
|
2841
2911
|
await replaceDirectory(
|
|
2842
2912
|
join16(templateDir, ".flydocs", "templates"),
|
|
@@ -3672,7 +3742,9 @@ var init_connect = __esm({
|
|
|
3672
3742
|
}
|
|
3673
3743
|
const wasLocal = config.tier === "local";
|
|
3674
3744
|
config.tier = "cloud";
|
|
3675
|
-
|
|
3745
|
+
const configRecord = config;
|
|
3746
|
+
delete configRecord.statusMapping;
|
|
3747
|
+
delete configRecord.provider;
|
|
3676
3748
|
await writeConfig(targetDir, config);
|
|
3677
3749
|
printStatus("Config updated to cloud tier");
|
|
3678
3750
|
if (wasLocal) {
|
package/package.json
CHANGED
|
@@ -35,7 +35,7 @@ All scripts: `python3 .claude/skills/flydocs-cloud/scripts/<script>`
|
|
|
35
35
|
| `comment.py` | `<ref> ["<comment>"] \| stdin` | `{success, commentId}` |
|
|
36
36
|
| `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}]` |
|
|
37
37
|
| `get_issue.py` | `<ref> [--fields basic\|full]` | `{id, identifier, title, description, status, assignee, priority, estimate, dueDate, milestone, milestoneId, project, projectId, comments[]}` |
|
|
38
|
-
| `assign.py` | `<ref> <assignee
|
|
38
|
+
| `assign.py` | `<ref> <assignee> \| --unassign` | `{success, issue, assignee}` |
|
|
39
39
|
| `update_description.py` | `<ref> --text "..." \| --file PATH \| stdin` | `{success, issue}` |
|
|
40
40
|
|
|
41
41
|
### Extended Scripts
|
|
@@ -74,6 +74,7 @@ All scripts: `python3 .claude/skills/flydocs-cloud/scripts/<script>`
|
|
|
74
74
|
| `set_preferences.py` | `[--workspace ID] [--assignee self\|ID] [--display JSON]` | `{success, preferences}` — no flags = GET current; with flags = POST update |
|
|
75
75
|
| `get_estimate_scale.py` | (no args) | `{scale, type}` — provider's valid estimate values (fixed or freeform) |
|
|
76
76
|
| `refresh_labels.py` | `[--fix]` | `{valid, stale, details}` — validates config label IDs against relay; `--fix` updates stale IDs |
|
|
77
|
+
| `validate_setup.py` | (no args) | `{valid, checks, missing[]}` — validates workspace config, caches result, sets setupComplete |
|
|
77
78
|
|
|
78
79
|
### Script Notes
|
|
79
80
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Assign an issue
|
|
2
|
+
"""Assign or unassign an issue via the FlyDocs Relay API."""
|
|
3
3
|
|
|
4
4
|
import sys
|
|
5
5
|
from pathlib import Path
|
|
@@ -7,10 +7,16 @@ from pathlib import Path
|
|
|
7
7
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
8
8
|
from flydocs_api import get_client, output_json, fail
|
|
9
9
|
|
|
10
|
-
if len(sys.argv) <
|
|
11
|
-
fail("Usage: assign.py <ref> <assignee>")
|
|
10
|
+
if len(sys.argv) < 2:
|
|
11
|
+
fail("Usage: assign.py <ref> <assignee> | assign.py <ref> --unassign")
|
|
12
12
|
|
|
13
|
-
ref
|
|
13
|
+
ref = sys.argv[1]
|
|
14
|
+
unassign = "--unassign" in sys.argv
|
|
15
|
+
|
|
16
|
+
if not unassign and len(sys.argv) < 3:
|
|
17
|
+
fail("Usage: assign.py <ref> <assignee> | assign.py <ref> --unassign")
|
|
18
|
+
|
|
19
|
+
assignee = None if unassign else sys.argv[2]
|
|
14
20
|
client = get_client()
|
|
15
21
|
|
|
16
22
|
result = client.post(f"/issues/{ref}/assign", {"assignee": assignee})
|
|
@@ -37,6 +37,12 @@ class FlyDocsClient:
|
|
|
37
37
|
print("Set in environment or .env/.env.local file", file=sys.stderr)
|
|
38
38
|
sys.exit(1)
|
|
39
39
|
|
|
40
|
+
self.workspace_id = self.config.get("workspaceId")
|
|
41
|
+
if not self.workspace_id:
|
|
42
|
+
print("ERROR: workspaceId not found in .flydocs/config.json", file=sys.stderr)
|
|
43
|
+
print("Run 'flydocs setup' to configure your workspace", file=sys.stderr)
|
|
44
|
+
sys.exit(1)
|
|
45
|
+
|
|
40
46
|
self.base_url = self._resolve_base_url()
|
|
41
47
|
|
|
42
48
|
def _load_config(self) -> dict:
|
|
@@ -93,6 +99,7 @@ class FlyDocsClient:
|
|
|
93
99
|
|
|
94
100
|
headers = {
|
|
95
101
|
"Authorization": f"Bearer {self.api_key}",
|
|
102
|
+
"X-Workspace": self.workspace_id,
|
|
96
103
|
"Content-Type": "application/json",
|
|
97
104
|
"Accept": "application/json",
|
|
98
105
|
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Validate workspace setup via the FlyDocs Relay API.
|
|
3
|
+
|
|
4
|
+
Read-only validation that checks provider, team, status mapping, label config,
|
|
5
|
+
and user identity. Writes result to .flydocs/validation-cache.json and sets
|
|
6
|
+
setupComplete: true in config when all required checks pass.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import sys
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
15
|
+
from flydocs_api import get_client, output_json, fail
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Required checks — failure blocks setup completion
|
|
19
|
+
REQUIRED_CHECKS = {
|
|
20
|
+
"provider": {
|
|
21
|
+
"test": lambda cfg: cfg.get("provider", {}).get("connected") is True,
|
|
22
|
+
"message": "No provider connected — configure in FlyDocs dashboard",
|
|
23
|
+
},
|
|
24
|
+
"team": {
|
|
25
|
+
"test": lambda cfg: bool(cfg.get("team", {}).get("id")),
|
|
26
|
+
"message": "No team selected — configure in FlyDocs dashboard",
|
|
27
|
+
},
|
|
28
|
+
"statusMapping": {
|
|
29
|
+
"test": lambda cfg: cfg.get("statusMapping", {}).get("configured") is True,
|
|
30
|
+
"message": "Status mapping not configured — configure in FlyDocs dashboard",
|
|
31
|
+
},
|
|
32
|
+
"labelConfig": {
|
|
33
|
+
"test": lambda cfg: cfg.get("labelConfig", {}).get("configured") is True,
|
|
34
|
+
"message": "Label config not configured — configure in FlyDocs dashboard",
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Optional checks — warn but don't block
|
|
39
|
+
OPTIONAL_CHECKS = {
|
|
40
|
+
"userIdentity": {
|
|
41
|
+
"test": lambda cfg: cfg.get("userIdentity", {}).get("linked") is True,
|
|
42
|
+
"message": (
|
|
43
|
+
"Provider identity not linked — run: "
|
|
44
|
+
"python3 .claude/skills/flydocs-cloud/scripts/set_identity.py <provider> <id>"
|
|
45
|
+
),
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def main() -> None:
|
|
51
|
+
client = get_client()
|
|
52
|
+
|
|
53
|
+
# Fetch workspace config from relay
|
|
54
|
+
config_response = client.get("/auth/config")
|
|
55
|
+
|
|
56
|
+
# Run checks
|
|
57
|
+
checks: dict[str, bool] = {}
|
|
58
|
+
valid: list[str] = []
|
|
59
|
+
missing: list[dict[str, str]] = []
|
|
60
|
+
warnings: list[dict[str, str]] = []
|
|
61
|
+
|
|
62
|
+
for name, check in REQUIRED_CHECKS.items():
|
|
63
|
+
passed = check["test"](config_response)
|
|
64
|
+
checks[name] = passed
|
|
65
|
+
if passed:
|
|
66
|
+
valid.append(name)
|
|
67
|
+
else:
|
|
68
|
+
missing.append({"check": name, "action": check["message"]})
|
|
69
|
+
|
|
70
|
+
for name, check in OPTIONAL_CHECKS.items():
|
|
71
|
+
passed = check["test"](config_response)
|
|
72
|
+
checks[name] = passed
|
|
73
|
+
if passed:
|
|
74
|
+
valid.append(name)
|
|
75
|
+
else:
|
|
76
|
+
warnings.append({"check": name, "action": check["message"]})
|
|
77
|
+
|
|
78
|
+
all_required_pass = len(missing) == 0
|
|
79
|
+
|
|
80
|
+
# Build workspace info from response
|
|
81
|
+
workspace = config_response.get("workspace", {})
|
|
82
|
+
provider_type = config_response.get("provider", {}).get("type", "unknown")
|
|
83
|
+
|
|
84
|
+
# Write validation cache
|
|
85
|
+
cache = {
|
|
86
|
+
"timestamp": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
|
87
|
+
"valid": all_required_pass,
|
|
88
|
+
"workspace": {
|
|
89
|
+
"id": workspace.get("id", ""),
|
|
90
|
+
"name": workspace.get("name", ""),
|
|
91
|
+
},
|
|
92
|
+
"provider": provider_type,
|
|
93
|
+
"checks": checks,
|
|
94
|
+
"missing": [m["check"] for m in missing],
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
cache_path = client.project_root / ".flydocs" / "validation-cache.json"
|
|
98
|
+
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
|
99
|
+
with open(cache_path, "w") as f:
|
|
100
|
+
json.dump(cache, f, indent=2)
|
|
101
|
+
f.write("\n")
|
|
102
|
+
|
|
103
|
+
# If all required checks pass, set setupComplete in config
|
|
104
|
+
if all_required_pass:
|
|
105
|
+
config_path = client.config_path
|
|
106
|
+
if config_path.exists():
|
|
107
|
+
with open(config_path, "r") as f:
|
|
108
|
+
local_config = json.load(f)
|
|
109
|
+
else:
|
|
110
|
+
local_config = {}
|
|
111
|
+
|
|
112
|
+
local_config["setupComplete"] = True
|
|
113
|
+
with open(config_path, "w") as f:
|
|
114
|
+
json.dump(local_config, f, indent=2)
|
|
115
|
+
f.write("\n")
|
|
116
|
+
|
|
117
|
+
# Output structured report
|
|
118
|
+
report: dict = {
|
|
119
|
+
"valid": all_required_pass,
|
|
120
|
+
"checks": checks,
|
|
121
|
+
"passed": valid,
|
|
122
|
+
}
|
|
123
|
+
if missing:
|
|
124
|
+
report["missing"] = missing
|
|
125
|
+
if warnings:
|
|
126
|
+
report["warnings"] = warnings
|
|
127
|
+
if all_required_pass:
|
|
128
|
+
report["setupComplete"] = True
|
|
129
|
+
|
|
130
|
+
output_json(report)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
main()
|
|
@@ -37,7 +37,7 @@ All scripts: `python3 .claude/skills/flydocs-local/scripts/<script>`
|
|
|
37
37
|
| `comment.py` | `<ref> ["<comment>"] \| stdin` | `{success, commentId}` |
|
|
38
38
|
| `list_issues.py` | `[--status STATUS] [--active] [--assignee STR] [--mine] [--limit N]` | `[{id, identifier, title, status, assignee, priority}]` |
|
|
39
39
|
| `get_issue.py` | `<ref>` | `{id, identifier, title, description, status, assignee, priority, estimate, comments[]}` |
|
|
40
|
-
| `assign.py` | `<ref> <assignee
|
|
40
|
+
| `assign.py` | `<ref> <assignee> \| --unassign` | `{success, issue, assignee}` |
|
|
41
41
|
| `update_description.py` | `<ref> --text "..." \| --file PATH \| stdin` | `{success, issue}` |
|
|
42
42
|
| `status_summary.py` | `[--project ID]` | `{statuses: {STATUS: count}, total}` |
|
|
43
43
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Assign an issue
|
|
2
|
+
"""Assign or unassign an issue."""
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
5
|
import sys
|
|
@@ -8,12 +8,21 @@ from pathlib import Path
|
|
|
8
8
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
9
9
|
from flydocs_api import assign_issue
|
|
10
10
|
|
|
11
|
-
if len(sys.argv) <
|
|
12
|
-
print("Usage: assign.py <ref> <assignee>", file=sys.stderr)
|
|
11
|
+
if len(sys.argv) < 2:
|
|
12
|
+
print("Usage: assign.py <ref> <assignee> | assign.py <ref> --unassign", file=sys.stderr)
|
|
13
13
|
sys.exit(1)
|
|
14
14
|
|
|
15
|
+
ref = sys.argv[1]
|
|
16
|
+
unassign = "--unassign" in sys.argv
|
|
17
|
+
|
|
18
|
+
if not unassign and len(sys.argv) < 3:
|
|
19
|
+
print("Usage: assign.py <ref> <assignee> | assign.py <ref> --unassign", file=sys.stderr)
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
|
|
22
|
+
assignee = None if unassign else sys.argv[2]
|
|
23
|
+
|
|
15
24
|
try:
|
|
16
|
-
result = assign_issue(
|
|
25
|
+
result = assign_issue(ref, assignee)
|
|
17
26
|
print(json.dumps(result))
|
|
18
27
|
except Exception as e:
|
|
19
28
|
print(str(e), file=sys.stderr)
|
|
@@ -236,10 +236,13 @@ def get_issue(ref: str) -> dict:
|
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
|
|
239
|
-
def assign_issue(ref: str, assignee: str) -> dict:
|
|
239
|
+
def assign_issue(ref: str, assignee: str | None) -> dict:
|
|
240
240
|
filepath = _find_issue(ref)
|
|
241
241
|
data = _parse_issue(filepath)
|
|
242
|
-
|
|
242
|
+
if assignee is None:
|
|
243
|
+
data.pop("assignee", None)
|
|
244
|
+
else:
|
|
245
|
+
data["assignee"] = assignee
|
|
243
246
|
data["updated"] = datetime.now().strftime("%Y-%m-%d")
|
|
244
247
|
|
|
245
248
|
fm = {k: v for k, v in data.items() if k not in ("description", "comments", "_path")}
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.6.0-alpha.
|
|
2
|
+
"version": "0.6.0-alpha.9",
|
|
3
3
|
"sourceRepo": "github.com/plastrlab/flydocs-core",
|
|
4
4
|
"tier": "local",
|
|
5
5
|
"setupComplete": false,
|
|
6
|
+
"workspaceId": null,
|
|
6
7
|
"paths": {
|
|
7
8
|
"content": "flydocs"
|
|
8
9
|
},
|
|
9
|
-
"provider": {
|
|
10
|
-
"type": null,
|
|
11
|
-
"teamId": null
|
|
12
|
-
},
|
|
13
10
|
"workspace": {
|
|
14
11
|
"activeProjects": [],
|
|
15
12
|
"defaultMilestoneId": null,
|
|
13
|
+
"repoSlug": null,
|
|
16
14
|
"product": {
|
|
17
15
|
"name": null,
|
|
18
16
|
"labelIds": [],
|
|
@@ -21,7 +19,7 @@
|
|
|
21
19
|
}
|
|
22
20
|
},
|
|
23
21
|
"issueLabels": {
|
|
24
|
-
"_note": "Run /flydocs-setup to populate these from your
|
|
22
|
+
"_note": "Run /flydocs-setup to populate these from your workspace",
|
|
25
23
|
"category": {
|
|
26
24
|
"feature": null,
|
|
27
25
|
"bug": null,
|
|
@@ -50,18 +48,6 @@
|
|
|
50
48
|
"installed": [],
|
|
51
49
|
"custom": []
|
|
52
50
|
},
|
|
53
|
-
"statusMapping": {
|
|
54
|
-
"BACKLOG": "Backlog",
|
|
55
|
-
"READY": "Todo",
|
|
56
|
-
"IMPLEMENTING": "In Progress",
|
|
57
|
-
"BLOCKED": "Blocked",
|
|
58
|
-
"REVIEW": "In Review",
|
|
59
|
-
"TESTING": "QA",
|
|
60
|
-
"COMPLETE": "Done",
|
|
61
|
-
"ARCHIVED": "Archived",
|
|
62
|
-
"CANCELED": "Canceled",
|
|
63
|
-
"DUPLICATE": "Duplicate"
|
|
64
|
-
},
|
|
65
51
|
"designSystem": null,
|
|
66
52
|
"aiLabor": {
|
|
67
53
|
"enabled": false,
|
|
@@ -168,15 +168,32 @@ def get_flydocs_version() -> str | None:
|
|
|
168
168
|
|
|
169
169
|
|
|
170
170
|
def get_setup_nudge() -> str | None:
|
|
171
|
-
"""Check if setup has been completed, return nudge if not.
|
|
171
|
+
"""Check if setup has been completed, return nudge if not.
|
|
172
|
+
|
|
173
|
+
Reads validation cache (written by validate_setup.py) for specific
|
|
174
|
+
missing items. Falls back to generic nudge if cache is absent.
|
|
175
|
+
"""
|
|
172
176
|
config_file = Path('.flydocs/config.json')
|
|
173
177
|
if not config_file.exists():
|
|
174
178
|
return None
|
|
175
179
|
try:
|
|
176
180
|
config = json.loads(config_file.read_text())
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
181
|
+
if config.get('setupComplete') is not False:
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
# Check validation cache for specific missing items
|
|
185
|
+
cache_file = Path('.flydocs/validation-cache.json')
|
|
186
|
+
if cache_file.exists():
|
|
187
|
+
try:
|
|
188
|
+
cache = json.loads(cache_file.read_text())
|
|
189
|
+
missing = cache.get('missing', [])
|
|
190
|
+
if missing:
|
|
191
|
+
items = ', '.join(missing)
|
|
192
|
+
return f'[Setup incomplete — missing: {items}. Run /flydocs-setup or fix in dashboard]'
|
|
193
|
+
except (json.JSONDecodeError, OSError, IOError):
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
return '[Setup incomplete — run /flydocs-setup to configure your project]'
|
|
180
197
|
except (json.JSONDecodeError, OSError, IOError):
|
|
181
198
|
pass
|
|
182
199
|
return None
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.6.0-alpha.
|
|
1
|
+
0.6.0-alpha.9
|
package/template/manifest.json
CHANGED