@flydocs/cli 0.6.0-alpha.11 → 0.6.0-alpha.12
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 +10 -1
- package/package.json +1 -1
- package/template/.claude/commands/flydocs-setup.md +38 -12
- package/template/.claude/skills/flydocs-cloud/SKILL.md +19 -16
- package/template/.claude/skills/flydocs-cloud/scripts/generate_config.py +125 -0
- package/template/.claude/skills/flydocs-cloud/scripts/get_me.py +103 -0
- package/template/.claude/skills/flydocs-cloud/scripts/set_identity.py +18 -2
- package/template/.claude/skills/flydocs-cloud/scripts/set_labels.py +0 -14
- package/template/.claude/skills/flydocs-cloud/scripts/set_provider.py +2 -17
- package/template/.claude/skills/flydocs-cloud/scripts/set_status_mapping.py +0 -12
- package/template/.claude/skills/flydocs-cloud/scripts/set_team.py +2 -16
- package/template/.claude/skills/flydocs-local/scripts/list_issues.py +17 -1
- package/template/.flydocs/config.json +1 -1
- package/template/.flydocs/hooks/prompt-submit.py +42 -1
- 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.12";
|
|
19
19
|
CLI_NAME = "flydocs";
|
|
20
20
|
PACKAGE_NAME = "@flydocs/cli";
|
|
21
21
|
POSTHOG_API_KEY = "phc_v1MSJTQDFkMS90CBh3mxIz3v8bYCCnKU6v1ir6bz0Xn";
|
|
@@ -294,6 +294,7 @@ function extractPreservedValues(config) {
|
|
|
294
294
|
tier: config.tier,
|
|
295
295
|
setupComplete: config.setupComplete ?? false,
|
|
296
296
|
workspaceId: config.workspaceId ?? null,
|
|
297
|
+
configVersion: config.configVersion,
|
|
297
298
|
workspace: config.workspace ?? {},
|
|
298
299
|
issueLabels: config.issueLabels ?? {},
|
|
299
300
|
detectedStack: config.detectedStack ?? {},
|
|
@@ -312,6 +313,9 @@ async function mergeConfig(templateDir, version, tierFlag, preserved) {
|
|
|
312
313
|
config.tier = tierFlag ?? preserved.tier;
|
|
313
314
|
config.setupComplete = preserved.setupComplete;
|
|
314
315
|
config.workspaceId = preserved.workspaceId;
|
|
316
|
+
if (preserved.configVersion !== void 0) {
|
|
317
|
+
config.configVersion = preserved.configVersion;
|
|
318
|
+
}
|
|
315
319
|
if (Object.keys(preserved.workspace).length > 0) {
|
|
316
320
|
config.workspace = preserved.workspace;
|
|
317
321
|
}
|
|
@@ -1523,6 +1527,8 @@ var init_gitignore = __esm({
|
|
|
1523
1527
|
".flydocs/session.json",
|
|
1524
1528
|
".flydocs/logs/",
|
|
1525
1529
|
".flydocs/backup-*/",
|
|
1530
|
+
".flydocs/me.json",
|
|
1531
|
+
".flydocs/validation-cache.json",
|
|
1526
1532
|
"flydocs/context/graph.json"
|
|
1527
1533
|
];
|
|
1528
1534
|
FULL_GITIGNORE_TEMPLATE = `# Environment
|
|
@@ -1534,6 +1540,8 @@ var init_gitignore = __esm({
|
|
|
1534
1540
|
.flydocs/session.json
|
|
1535
1541
|
.flydocs/logs/
|
|
1536
1542
|
.flydocs/backup-*/
|
|
1543
|
+
.flydocs/me.json
|
|
1544
|
+
.flydocs/validation-cache.json
|
|
1537
1545
|
flydocs/context/graph.json
|
|
1538
1546
|
|
|
1539
1547
|
# Dependencies
|
|
@@ -2881,6 +2889,7 @@ var init_update = __esm({
|
|
|
2881
2889
|
tier: "cloud",
|
|
2882
2890
|
setupComplete: false,
|
|
2883
2891
|
workspaceId: null,
|
|
2892
|
+
configVersion: void 0,
|
|
2884
2893
|
workspace: {},
|
|
2885
2894
|
issueLabels: {},
|
|
2886
2895
|
detectedStack: {},
|
package/package.json
CHANGED
|
@@ -470,17 +470,38 @@ Ask about product metadata:
|
|
|
470
470
|
from project.md)
|
|
471
471
|
- **Icon and color** — optional, ask if they have preferences
|
|
472
472
|
|
|
473
|
-
**Step 8:
|
|
473
|
+
**Step 8: Generate config from relay.**
|
|
474
474
|
|
|
475
|
-
|
|
475
|
+
The setup scripts in Steps 2-7 all POST to the relay — they no longer write
|
|
476
|
+
to local config. After all setup steps complete, pull the canonical config:
|
|
476
477
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
- `
|
|
478
|
+
```bash
|
|
479
|
+
python3 .claude/skills/flydocs-cloud/scripts/generate_config.py
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
This calls `GET /config/generate` which returns all server-owned fields
|
|
483
|
+
(`workspaceId`, `setupComplete`, `workspace`, `issueLabels`). The script
|
|
484
|
+
merges these with local-only fields (`tier`, `detectedStack`, `skills`,
|
|
485
|
+
`designSystem`, `aiLabor`, `paths`) and writes `.flydocs/config.json`.
|
|
486
|
+
|
|
487
|
+
It also cleans up ghost fields (`provider`, `statusMapping`, `labels`) that
|
|
488
|
+
were previously written by individual setup scripts but aren't in the TS
|
|
489
|
+
type system.
|
|
490
|
+
|
|
491
|
+
If the response includes `missing` or `warnings`, show them to the user
|
|
492
|
+
but don't block — they can fix in the dashboard and re-run later.
|
|
493
|
+
|
|
494
|
+
**Step 9: Fetch user identity.**
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
python3 .claude/skills/flydocs-cloud/scripts/get_me.py
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
This calls `GET /auth/me` (no workspace required) and writes
|
|
501
|
+
`.flydocs/me.json` with `displayName`, `email`, `providerId`, and
|
|
502
|
+
`providerIdentities`. The file is gitignored — each developer gets their own.
|
|
503
|
+
|
|
504
|
+
This enables `--mine` filters and personalizes session output.
|
|
484
505
|
|
|
485
506
|
---
|
|
486
507
|
|
|
@@ -677,9 +698,14 @@ Tier: [local / cloud]
|
|
|
677
698
|
|
|
678
699
|
**Mark setup as complete.**
|
|
679
700
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
701
|
+
For **cloud tier**: Setup completion is determined by the relay. The
|
|
702
|
+
`generate_config.py` call in Phase 2 Step 8 already sets `setupComplete`
|
|
703
|
+
based on the relay's validation result. If the relay returns `valid: true`,
|
|
704
|
+
config will have `setupComplete: true`. If not, run `validate_setup.py` to
|
|
705
|
+
check what's still missing.
|
|
706
|
+
|
|
707
|
+
For **local tier**: Update `.flydocs/config.json` to set `setupComplete` to
|
|
708
|
+
`true`. Read the config, set the field, and write it back:
|
|
683
709
|
|
|
684
710
|
```json
|
|
685
711
|
{ "setupComplete": true }
|
|
@@ -59,26 +59,29 @@ All scripts: `python3 .claude/skills/flydocs-cloud/scripts/<script>`
|
|
|
59
59
|
|
|
60
60
|
### Workspace Scripts
|
|
61
61
|
|
|
62
|
-
| Script | Usage | Output
|
|
63
|
-
| ----------------------- | --------------------------------------------------------------------- |
|
|
64
|
-
| `list_providers.py` | (no args) | `[{type, name, connected}]`
|
|
65
|
-
| `set_provider.py` | `<provider_type>` (`linear` or `jira`) | `{success}` — updates relay routing
|
|
66
|
-
| `list_teams.py` | (no args) | `[{id, name, key}]` — returns Linear teams or Jira projects (relay normalizes)
|
|
67
|
-
| `create_team.py` | `--name "..." [--key KEY] [--description "..."] [--parent <team_id>]` | `{id, name, key}` — `--parent` is Linear-only (sub-teams)
|
|
68
|
-
| `set_team.py` | `<team_id>` | `{success}` — updates relay preference
|
|
69
|
-
| `list_labels.py` | (no args) | `[{id, name, color}]` — requires team to be set first
|
|
70
|
-
| `set_labels.py` | `--defaults '["a"]' --type-map '{"feature":["F"],...}' \| stdin` | `{success, validated, defaults, typeMap}` — stores label config on relay
|
|
71
|
-
| `list_statuses.py` | (no args) | `{states, currentMapping, flydocsStatuses}` — provider workflow states and current mapping
|
|
72
|
-
| `set_status_mapping.py` | `--auto \| --mapping '{"BACKLOG":"Backlog",...}' \| stdin` | `{success, mapping, matched, total}` — stores status mapping on relay
|
|
73
|
-
| `set_identity.py` | `<provider> <provider-user-id>` | `{success, provider, providerId}` — binds provider user ID for `--mine` resolution
|
|
74
|
-
| `set_preferences.py` | `[--workspace ID] [--assignee self\|ID] [--display JSON]` | `{success, preferences}` — no flags = GET current; with flags = POST update
|
|
75
|
-
| `get_estimate_scale.py` | (no args) | `{scale, type}` — provider's valid estimate values (fixed or freeform)
|
|
76
|
-
| `refresh_labels.py` | `[--fix]` | `{valid, stale, details}` — validates config label IDs against relay; `--fix` updates stale IDs
|
|
77
|
-
| `
|
|
62
|
+
| Script | Usage | Output |
|
|
63
|
+
| ----------------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
64
|
+
| `list_providers.py` | (no args) | `[{type, name, connected}]` |
|
|
65
|
+
| `set_provider.py` | `<provider_type>` (`linear` or `jira`) | `{success}` — updates relay routing (no local config write) |
|
|
66
|
+
| `list_teams.py` | (no args) | `[{id, name, key}]` — returns Linear teams or Jira projects (relay normalizes) |
|
|
67
|
+
| `create_team.py` | `--name "..." [--key KEY] [--description "..."] [--parent <team_id>]` | `{id, name, key}` — `--parent` is Linear-only (sub-teams) |
|
|
68
|
+
| `set_team.py` | `<team_id>` | `{success}` — updates relay preference (no local config write); for Jira, sets the active Jira project |
|
|
69
|
+
| `list_labels.py` | (no args) | `[{id, name, color}]` — requires team to be set first |
|
|
70
|
+
| `set_labels.py` | `--defaults '["a"]' --type-map '{"feature":["F"],...}' \| stdin` | `{success, validated, defaults, typeMap}` — stores label config on relay |
|
|
71
|
+
| `list_statuses.py` | (no args) | `{states, currentMapping, flydocsStatuses}` — provider workflow states and current mapping |
|
|
72
|
+
| `set_status_mapping.py` | `--auto \| --mapping '{"BACKLOG":"Backlog",...}' \| stdin` | `{success, mapping, matched, total}` — stores status mapping on relay |
|
|
73
|
+
| `set_identity.py` | `<provider> <provider-user-id>` | `{success, provider, providerId, meJson}` — binds provider user ID for `--mine` resolution; writes `.flydocs/me.json` |
|
|
74
|
+
| `set_preferences.py` | `[--workspace ID] [--assignee self\|ID] [--display JSON]` | `{success, preferences}` — no flags = GET current; with flags = POST update |
|
|
75
|
+
| `get_estimate_scale.py` | (no args) | `{scale, type}` — provider's valid estimate values (fixed or freeform) |
|
|
76
|
+
| `refresh_labels.py` | `[--fix]` | `{valid, stale, details}` — validates config label IDs against relay; `--fix` updates stale IDs |
|
|
77
|
+
| `generate_config.py` | `[--dry-run]` | `{success, configVersion, valid, missing[], warnings[]}` — pulls server config, merges with local, writes config.json |
|
|
78
|
+
| `get_me.py` | (no args) | `{success, displayName, email, provider, meJson}` — fetches user identity, writes `.flydocs/me.json` (no workspace) |
|
|
79
|
+
| `validate_setup.py` | (no args) | `{valid, checks, passed[], missing[], warnings[]}` — reads relay validation, caches result, sets setupComplete |
|
|
78
80
|
|
|
79
81
|
### Script Notes
|
|
80
82
|
|
|
81
83
|
- **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. For Jira, the relay handles Markdown-to-ADF (Atlassian Document Format) conversion automatically.
|
|
84
|
+
- **Config cascade**: Setup scripts (`set_provider`, `set_team`, `set_labels`, `set_status_mapping`) only POST to the relay — they do not write local config. After all setup steps, run `generate_config.py` to pull the canonical server config and merge with local-only fields. This eliminates ghost fields and ensures config.json stays in sync with the relay.
|
|
82
85
|
- **`list_issues.py --active`**: Returns all non-terminal issues (excludes Done, Archived, Canceled, Duplicate).
|
|
83
86
|
- **`list_issues.py --status`**: Accepts comma-separated statuses: `--status READY,IMPLEMENTING,BLOCKED`
|
|
84
87
|
- **`get_issue.py --fields basic`**: Skips comment fetch for faster responses.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generate config.json from the relay's canonical workspace config.
|
|
3
|
+
|
|
4
|
+
Calls GET /config/generate which returns server-owned fields (workspace,
|
|
5
|
+
issueLabels, provider, statusMapping, etc.). Merges with local-only fields
|
|
6
|
+
(tier, detectedStack, skills, designSystem, aiLabor, paths) to produce
|
|
7
|
+
the final .flydocs/config.json.
|
|
8
|
+
|
|
9
|
+
This replaces the pattern where each setup script (set_provider, set_team,
|
|
10
|
+
set_labels, set_status_mapping) independently wrote to local config.
|
|
11
|
+
Now those scripts only POST to the relay, and this script pulls the
|
|
12
|
+
merged result once at the end of setup.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
python3 generate_config.py
|
|
16
|
+
python3 generate_config.py --dry-run # Print merged config without writing
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import json
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
25
|
+
from flydocs_api import get_client, output_json, fail
|
|
26
|
+
|
|
27
|
+
# Fields owned by the server — overwritten from generate response
|
|
28
|
+
SERVER_OWNED_FIELDS = {
|
|
29
|
+
"workspaceId",
|
|
30
|
+
"setupComplete",
|
|
31
|
+
"workspace",
|
|
32
|
+
"issueLabels",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Fields owned locally — never overwritten by generate
|
|
36
|
+
LOCAL_ONLY_FIELDS = {
|
|
37
|
+
"version",
|
|
38
|
+
"sourceRepo",
|
|
39
|
+
"tier",
|
|
40
|
+
"paths",
|
|
41
|
+
"detectedStack",
|
|
42
|
+
"skills",
|
|
43
|
+
"designSystem",
|
|
44
|
+
"aiLabor",
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def main() -> None:
|
|
49
|
+
parser = argparse.ArgumentParser(description="Generate config from relay")
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--dry-run",
|
|
52
|
+
action="store_true",
|
|
53
|
+
help="Print merged config without writing to disk",
|
|
54
|
+
)
|
|
55
|
+
args = parser.parse_args()
|
|
56
|
+
|
|
57
|
+
client = get_client()
|
|
58
|
+
|
|
59
|
+
# Fetch server-owned config from relay
|
|
60
|
+
response = client.get("/config/generate")
|
|
61
|
+
|
|
62
|
+
if not response.get("valid", False):
|
|
63
|
+
missing = response.get("missing", [])
|
|
64
|
+
warnings = response.get("warnings", [])
|
|
65
|
+
parts = []
|
|
66
|
+
if missing:
|
|
67
|
+
parts.append(f"missing: {', '.join(missing)}")
|
|
68
|
+
if warnings:
|
|
69
|
+
parts.append(f"warnings: {', '.join(warnings)}")
|
|
70
|
+
detail = "; ".join(parts) if parts else "unknown reason"
|
|
71
|
+
print(f"WARNING: Config not fully valid — {detail}", file=sys.stderr)
|
|
72
|
+
|
|
73
|
+
server_config = response.get("config", {})
|
|
74
|
+
|
|
75
|
+
# Read existing local config
|
|
76
|
+
config_path = client.config_path
|
|
77
|
+
if config_path.exists():
|
|
78
|
+
with open(config_path, "r") as f:
|
|
79
|
+
local_config = json.load(f)
|
|
80
|
+
else:
|
|
81
|
+
local_config = {}
|
|
82
|
+
|
|
83
|
+
# Merge: server-owned fields overwrite, local-only fields preserved
|
|
84
|
+
merged = dict(local_config)
|
|
85
|
+
|
|
86
|
+
# Apply server-owned fields
|
|
87
|
+
if "workspaceId" in server_config:
|
|
88
|
+
merged["workspaceId"] = server_config["workspaceId"]
|
|
89
|
+
if "setupComplete" in server_config:
|
|
90
|
+
merged["setupComplete"] = server_config["setupComplete"]
|
|
91
|
+
if "workspace" in server_config:
|
|
92
|
+
merged["workspace"] = server_config["workspace"]
|
|
93
|
+
if "issueLabels" in server_config:
|
|
94
|
+
merged["issueLabels"] = server_config["issueLabels"]
|
|
95
|
+
|
|
96
|
+
# Store configVersion for freshness tracking
|
|
97
|
+
if "configVersion" in response:
|
|
98
|
+
merged["configVersion"] = response["configVersion"]
|
|
99
|
+
|
|
100
|
+
# Clean up ghost fields that are now server-owned
|
|
101
|
+
# These were written by old setup scripts but aren't in the TS type system
|
|
102
|
+
for ghost in ("provider", "statusMapping", "labels"):
|
|
103
|
+
merged.pop(ghost, None)
|
|
104
|
+
|
|
105
|
+
if args.dry_run:
|
|
106
|
+
output_json(merged)
|
|
107
|
+
return
|
|
108
|
+
|
|
109
|
+
# Write merged config
|
|
110
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
111
|
+
with open(config_path, "w") as f:
|
|
112
|
+
json.dump(merged, f, indent=2)
|
|
113
|
+
f.write("\n")
|
|
114
|
+
|
|
115
|
+
output_json({
|
|
116
|
+
"success": True,
|
|
117
|
+
"configVersion": response.get("configVersion"),
|
|
118
|
+
"valid": response.get("valid", False),
|
|
119
|
+
"missing": response.get("missing", []),
|
|
120
|
+
"warnings": response.get("warnings", []),
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
if __name__ == "__main__":
|
|
125
|
+
main()
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Fetch current user identity from the FlyDocs Relay API.
|
|
3
|
+
|
|
4
|
+
Calls GET /auth/me (no X-Workspace needed — user-scoped endpoint).
|
|
5
|
+
Writes .flydocs/me.json for local identity resolution (--mine filters,
|
|
6
|
+
display name in comments, etc.). This file is gitignored.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python3 get_me.py
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
18
|
+
from flydocs_api import find_project_root, output_json, fail
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_api_key(project_root: Path) -> str | None:
|
|
22
|
+
"""Load API key from environment or .env files."""
|
|
23
|
+
if os.environ.get("FLYDOCS_API_KEY"):
|
|
24
|
+
return os.environ["FLYDOCS_API_KEY"]
|
|
25
|
+
for name in [".env.local", ".env"]:
|
|
26
|
+
env_file = project_root / name
|
|
27
|
+
if env_file.exists():
|
|
28
|
+
with open(env_file, "r") as f:
|
|
29
|
+
for line in f:
|
|
30
|
+
line = line.strip()
|
|
31
|
+
if line.startswith("#") or "=" not in line:
|
|
32
|
+
continue
|
|
33
|
+
k, _, v = line.partition("=")
|
|
34
|
+
if k.strip() == "FLYDOCS_API_KEY":
|
|
35
|
+
v = v.strip().strip("\"'")
|
|
36
|
+
return v if v else None
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def resolve_base_url() -> str:
|
|
41
|
+
"""Resolve relay base URL from environment."""
|
|
42
|
+
env_url = os.environ.get("FLYDOCS_RELAY_URL")
|
|
43
|
+
if env_url:
|
|
44
|
+
return env_url.rstrip("/")
|
|
45
|
+
return "https://app.flydocs.ai/api/relay"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def main() -> None:
|
|
49
|
+
import urllib.request
|
|
50
|
+
import urllib.error
|
|
51
|
+
|
|
52
|
+
project_root = find_project_root()
|
|
53
|
+
api_key = load_api_key(project_root)
|
|
54
|
+
if not api_key:
|
|
55
|
+
fail("FLYDOCS_API_KEY not found. Set in environment or .env/.env.local file")
|
|
56
|
+
|
|
57
|
+
base_url = resolve_base_url()
|
|
58
|
+
url = f"{base_url}/auth/me"
|
|
59
|
+
|
|
60
|
+
headers = {
|
|
61
|
+
"Authorization": f"Bearer {api_key}",
|
|
62
|
+
"Accept": "application/json",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
req = urllib.request.Request(url, headers=headers, method="GET")
|
|
67
|
+
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
68
|
+
result = json.loads(resp.read().decode("utf-8"))
|
|
69
|
+
except urllib.error.HTTPError as e:
|
|
70
|
+
error_body = e.read().decode("utf-8") if e.fp else ""
|
|
71
|
+
try:
|
|
72
|
+
error_data = json.loads(error_body)
|
|
73
|
+
except json.JSONDecodeError:
|
|
74
|
+
error_data = {"error": error_body}
|
|
75
|
+
fail(f"API error ({e.code}): {error_data.get('error', 'Unknown')}")
|
|
76
|
+
except (urllib.error.URLError, TimeoutError):
|
|
77
|
+
fail("Network error: unable to reach relay API")
|
|
78
|
+
|
|
79
|
+
# Write me.json
|
|
80
|
+
me_data = {
|
|
81
|
+
"displayName": result.get("displayName"),
|
|
82
|
+
"email": result.get("email"),
|
|
83
|
+
"providerId": result.get("providerId"),
|
|
84
|
+
"provider": result.get("provider"),
|
|
85
|
+
"providerIdentities": result.get("providerIdentities", []),
|
|
86
|
+
"preferences": result.get("preferences", {}),
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
me_path = project_root / ".flydocs" / "me.json"
|
|
90
|
+
me_path.parent.mkdir(parents=True, exist_ok=True)
|
|
91
|
+
me_path.write_text(json.dumps(me_data, indent=2) + "\n")
|
|
92
|
+
|
|
93
|
+
output_json({
|
|
94
|
+
"success": True,
|
|
95
|
+
"displayName": me_data["displayName"],
|
|
96
|
+
"email": me_data["email"],
|
|
97
|
+
"provider": me_data["provider"],
|
|
98
|
+
"meJson": str(me_path),
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
main()
|
|
@@ -3,8 +3,12 @@
|
|
|
3
3
|
|
|
4
4
|
Binds the user's provider-specific ID to their FlyDocs user record.
|
|
5
5
|
Once set, ?mine=true resolves via exact provider ID matching.
|
|
6
|
+
|
|
7
|
+
Also writes .flydocs/me.json for local identity resolution (e.g. --mine
|
|
8
|
+
in list_issues.py). This file is gitignored — each developer has their own.
|
|
6
9
|
"""
|
|
7
10
|
|
|
11
|
+
import json
|
|
8
12
|
import sys
|
|
9
13
|
from pathlib import Path
|
|
10
14
|
|
|
@@ -31,8 +35,20 @@ result = client.post("/auth/identity", {
|
|
|
31
35
|
"providerId": provider_id,
|
|
32
36
|
})
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
# Write me.json for local identity resolution
|
|
39
|
+
me_data = {
|
|
36
40
|
"provider": result.get("provider", provider),
|
|
37
41
|
"providerId": result.get("providerId", provider_id),
|
|
42
|
+
"displayName": result.get("displayName"),
|
|
43
|
+
"email": result.get("email"),
|
|
44
|
+
}
|
|
45
|
+
me_path = Path(".flydocs/me.json")
|
|
46
|
+
me_path.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
me_path.write_text(json.dumps(me_data, indent=2) + "\n")
|
|
48
|
+
|
|
49
|
+
output_json({
|
|
50
|
+
"success": result.get("success", True),
|
|
51
|
+
"provider": me_data["provider"],
|
|
52
|
+
"providerId": me_data["providerId"],
|
|
53
|
+
"meJson": str(me_path),
|
|
38
54
|
})
|
|
@@ -47,20 +47,6 @@ def main():
|
|
|
47
47
|
|
|
48
48
|
client = get_client()
|
|
49
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
50
|
output_json(result)
|
|
65
51
|
|
|
66
52
|
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""Set provider preference via the FlyDocs Relay API.
|
|
3
3
|
|
|
4
|
-
Stores the provider type on the relay
|
|
5
|
-
|
|
4
|
+
Stores the provider type on the relay for server-side routing.
|
|
5
|
+
Local config is updated by generate_config.py after all setup steps complete.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import argparse
|
|
9
|
-
import json
|
|
10
9
|
import sys
|
|
11
10
|
from pathlib import Path
|
|
12
11
|
|
|
@@ -25,20 +24,6 @@ def main():
|
|
|
25
24
|
|
|
26
25
|
client = get_client()
|
|
27
26
|
result = client.post("/auth/provider", {"providerType": args.provider_type})
|
|
28
|
-
|
|
29
|
-
# Update local config with provider type
|
|
30
|
-
config_path = client.config_path
|
|
31
|
-
if config_path.exists():
|
|
32
|
-
with open(config_path, "r") as f:
|
|
33
|
-
config = json.load(f)
|
|
34
|
-
if "provider" not in config:
|
|
35
|
-
config["provider"] = {"type": args.provider_type, "teamId": None}
|
|
36
|
-
else:
|
|
37
|
-
config["provider"]["type"] = args.provider_type
|
|
38
|
-
with open(config_path, "w") as f:
|
|
39
|
-
json.dump(config, f, indent=2)
|
|
40
|
-
f.write("\n")
|
|
41
|
-
|
|
42
27
|
output_json(result)
|
|
43
28
|
|
|
44
29
|
|
|
@@ -50,18 +50,6 @@ def main():
|
|
|
50
50
|
|
|
51
51
|
client = get_client()
|
|
52
52
|
result = client.post("/auth/statuses", body)
|
|
53
|
-
|
|
54
|
-
# Store status mapping in local config as reference
|
|
55
|
-
config_path = client.config_path
|
|
56
|
-
if config_path.exists():
|
|
57
|
-
with open(config_path, "r") as f:
|
|
58
|
-
config = json.load(f)
|
|
59
|
-
if isinstance(result.get("mapping"), dict):
|
|
60
|
-
config["statusMapping"] = result["mapping"]
|
|
61
|
-
with open(config_path, "w") as f:
|
|
62
|
-
json.dump(config, f, indent=2)
|
|
63
|
-
f.write("\n")
|
|
64
|
-
|
|
65
53
|
output_json(result)
|
|
66
54
|
|
|
67
55
|
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""Set team/project preference via the FlyDocs Relay API.
|
|
3
3
|
|
|
4
|
-
Stores the team preference on the relay
|
|
5
|
-
|
|
4
|
+
Stores the team preference on the relay for server-side scoping.
|
|
5
|
+
Local config is updated by generate_config.py after all setup steps complete.
|
|
6
6
|
For Jira, this sets the active Jira project.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
import argparse
|
|
10
|
-
import json
|
|
11
10
|
import sys
|
|
12
11
|
from pathlib import Path
|
|
13
12
|
|
|
@@ -22,19 +21,6 @@ def main():
|
|
|
22
21
|
|
|
23
22
|
client = get_client()
|
|
24
23
|
result = client.post("/auth/team", {"teamId": args.team_id})
|
|
25
|
-
|
|
26
|
-
# Update local config with team ID
|
|
27
|
-
config_path = client.config_path
|
|
28
|
-
if config_path.exists():
|
|
29
|
-
with open(config_path, "r") as f:
|
|
30
|
-
config = json.load(f)
|
|
31
|
-
if "provider" not in config:
|
|
32
|
-
config["provider"] = {"type": None, "teamId": None}
|
|
33
|
-
config["provider"]["teamId"] = args.team_id
|
|
34
|
-
with open(config_path, "w") as f:
|
|
35
|
-
json.dump(config, f, indent=2)
|
|
36
|
-
f.write("\n")
|
|
37
|
-
|
|
38
24
|
output_json(result)
|
|
39
25
|
|
|
40
26
|
|
|
@@ -12,6 +12,22 @@ from flydocs_api import list_issues
|
|
|
12
12
|
|
|
13
13
|
TERMINAL_STATUSES = {"COMPLETE", "ARCHIVED", "CANCELED", "DUPLICATE"}
|
|
14
14
|
|
|
15
|
+
|
|
16
|
+
def resolve_identity() -> str:
|
|
17
|
+
"""Resolve current user identity from me.json, falling back to $USER."""
|
|
18
|
+
me_path = Path(".flydocs/me.json")
|
|
19
|
+
if me_path.exists():
|
|
20
|
+
try:
|
|
21
|
+
me = json.loads(me_path.read_text())
|
|
22
|
+
# Prefer displayName for local file matching, fall back to email
|
|
23
|
+
name = me.get("displayName") or me.get("email") or ""
|
|
24
|
+
if name:
|
|
25
|
+
return name
|
|
26
|
+
except (json.JSONDecodeError, OSError):
|
|
27
|
+
pass
|
|
28
|
+
return os.environ.get("USER", os.environ.get("USERNAME", ""))
|
|
29
|
+
|
|
30
|
+
|
|
15
31
|
parser = argparse.ArgumentParser(description="List issues")
|
|
16
32
|
parser.add_argument("--status", default="")
|
|
17
33
|
parser.add_argument("--active", action="store_true",
|
|
@@ -24,7 +40,7 @@ try:
|
|
|
24
40
|
args = parser.parse_args()
|
|
25
41
|
assignee = args.assignee
|
|
26
42
|
if args.mine:
|
|
27
|
-
assignee =
|
|
43
|
+
assignee = resolve_identity()
|
|
28
44
|
result = list_issues(status=args.status.upper() if args.status else "", assignee=assignee, limit=args.limit)
|
|
29
45
|
if args.active:
|
|
30
46
|
result = [r for r in result if r.get("status") not in TERMINAL_STATUSES]
|
|
@@ -205,6 +205,43 @@ def get_setup_nudge() -> str | None:
|
|
|
205
205
|
return None
|
|
206
206
|
|
|
207
207
|
|
|
208
|
+
def get_config_freshness_nudge() -> str | None:
|
|
209
|
+
"""Nudge if validation cache is stale (>24h old).
|
|
210
|
+
|
|
211
|
+
Only applies to cloud tier with setupComplete=true. Encourages
|
|
212
|
+
periodic re-validation so config stays in sync with the server.
|
|
213
|
+
"""
|
|
214
|
+
config_file = Path('.flydocs/config.json')
|
|
215
|
+
if not config_file.exists():
|
|
216
|
+
return None
|
|
217
|
+
try:
|
|
218
|
+
config = json.loads(config_file.read_text())
|
|
219
|
+
# Only check freshness for cloud tier with completed setup
|
|
220
|
+
if config.get('tier') != 'cloud' or config.get('setupComplete') is not True:
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
cache_file = Path('.flydocs/validation-cache.json')
|
|
224
|
+
if not cache_file.exists():
|
|
225
|
+
return '[Config not validated — run: python3 .claude/skills/flydocs-cloud/scripts/validate_setup.py]'
|
|
226
|
+
|
|
227
|
+
from datetime import datetime, timezone
|
|
228
|
+
cache = json.loads(cache_file.read_text())
|
|
229
|
+
timestamp_str = cache.get('timestamp')
|
|
230
|
+
if not timestamp_str:
|
|
231
|
+
return None
|
|
232
|
+
|
|
233
|
+
# Parse ISO timestamp
|
|
234
|
+
cached_at = datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))
|
|
235
|
+
now = datetime.now(timezone.utc)
|
|
236
|
+
age_hours = (now - cached_at).total_seconds() / 3600
|
|
237
|
+
|
|
238
|
+
if age_hours > 24:
|
|
239
|
+
return '[Config stale (>24h) — run: python3 .claude/skills/flydocs-cloud/scripts/validate_setup.py]'
|
|
240
|
+
except (json.JSONDecodeError, OSError, IOError, ValueError):
|
|
241
|
+
pass
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
|
|
208
245
|
def main() -> None:
|
|
209
246
|
"""Main hook execution."""
|
|
210
247
|
debug_log('=== Hook invoked ===')
|
|
@@ -268,10 +305,14 @@ def main() -> None:
|
|
|
268
305
|
if ac_progress:
|
|
269
306
|
context_parts.append(ac_progress)
|
|
270
307
|
|
|
271
|
-
# Setup completion nudge
|
|
308
|
+
# Setup completion nudge OR config freshness nudge (mutually exclusive)
|
|
272
309
|
setup_nudge = get_setup_nudge()
|
|
273
310
|
if setup_nudge:
|
|
274
311
|
context_parts.append(setup_nudge)
|
|
312
|
+
else:
|
|
313
|
+
freshness_nudge = get_config_freshness_nudge()
|
|
314
|
+
if freshness_nudge:
|
|
315
|
+
context_parts.append(freshness_nudge)
|
|
275
316
|
|
|
276
317
|
# FlyDocs version
|
|
277
318
|
version = get_flydocs_version()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.6.0-alpha.
|
|
1
|
+
0.6.0-alpha.12
|
package/template/manifest.json
CHANGED