@flydocs/cli 0.5.0-beta.0
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 +96 -0
- package/dist/cli.js +2666 -0
- package/package.json +32 -0
- package/template/.claude/CLAUDE.md +90 -0
- package/template/.claude/agents/README.md +19 -0
- package/template/.claude/agents/implementation-agent.md +29 -0
- package/template/.claude/agents/pm-agent.md +29 -0
- package/template/.claude/agents/research-agent.md +25 -0
- package/template/.claude/agents/review-agent.md +29 -0
- package/template/.claude/commands/activate.md +10 -0
- package/template/.claude/commands/attach.md +9 -0
- package/template/.claude/commands/block.md +10 -0
- package/template/.claude/commands/capture.md +10 -0
- package/template/.claude/commands/close.md +10 -0
- package/template/.claude/commands/flydocs-setup.md +598 -0
- package/template/.claude/commands/flydocs-update.md +27 -0
- package/template/.claude/commands/implement.md +10 -0
- package/template/.claude/commands/new-project.md +11 -0
- package/template/.claude/commands/project-update.md +10 -0
- package/template/.claude/commands/refine.md +10 -0
- package/template/.claude/commands/review.md +10 -0
- package/template/.claude/commands/start-session.md +10 -0
- package/template/.claude/commands/status.md +10 -0
- package/template/.claude/commands/validate.md +10 -0
- package/template/.claude/commands/wrap-session.md +10 -0
- package/template/.claude/settings.json +49 -0
- package/template/.claude/skills/README.md +293 -0
- package/template/.claude/skills/flydocs-cloud/SKILL.md +96 -0
- package/template/.claude/skills/flydocs-cloud/cursor-rule.mdc +50 -0
- package/template/.claude/skills/flydocs-cloud/scripts/assign.py +38 -0
- package/template/.claude/skills/flydocs-cloud/scripts/assign_cycle.py +44 -0
- package/template/.claude/skills/flydocs-cloud/scripts/assign_milestone.py +44 -0
- package/template/.claude/skills/flydocs-cloud/scripts/comment.py +39 -0
- package/template/.claude/skills/flydocs-cloud/scripts/create_issue.py +100 -0
- package/template/.claude/skills/flydocs-cloud/scripts/create_milestone.py +46 -0
- package/template/.claude/skills/flydocs-cloud/scripts/create_project.py +40 -0
- package/template/.claude/skills/flydocs-cloud/scripts/estimate.py +38 -0
- package/template/.claude/skills/flydocs-cloud/scripts/flydocs_api.py +277 -0
- package/template/.claude/skills/flydocs-cloud/scripts/get_issue.py +77 -0
- package/template/.claude/skills/flydocs-cloud/scripts/link.py +47 -0
- package/template/.claude/skills/flydocs-cloud/scripts/list_cycles.py +35 -0
- package/template/.claude/skills/flydocs-cloud/scripts/list_issues.py +105 -0
- package/template/.claude/skills/flydocs-cloud/scripts/list_milestones.py +40 -0
- package/template/.claude/skills/flydocs-cloud/scripts/list_projects.py +45 -0
- package/template/.claude/skills/flydocs-cloud/scripts/priority.py +38 -0
- package/template/.claude/skills/flydocs-cloud/scripts/project_update.py +59 -0
- package/template/.claude/skills/flydocs-cloud/scripts/transition.py +67 -0
- package/template/.claude/skills/flydocs-cloud/scripts/update_description.py +47 -0
- package/template/.claude/skills/flydocs-cloud/scripts/update_issue.py +111 -0
- package/template/.claude/skills/flydocs-context-graph/SKILL.md +87 -0
- package/template/.claude/skills/flydocs-context-graph/schema.md +78 -0
- package/template/.claude/skills/flydocs-context-graph/scripts/graph_build.py +299 -0
- package/template/.claude/skills/flydocs-context-graph/scripts/graph_context.py +338 -0
- package/template/.claude/skills/flydocs-context-graph/scripts/graph_query.py +191 -0
- package/template/.claude/skills/flydocs-context-graph/scripts/graph_session.py +161 -0
- package/template/.claude/skills/flydocs-context-graph/scripts/graph_update.py +194 -0
- package/template/.claude/skills/flydocs-context-graph/scripts/graph_utils.py +118 -0
- package/template/.claude/skills/flydocs-estimates/SKILL.md +384 -0
- package/template/.claude/skills/flydocs-estimates/references/provider-costs.md +152 -0
- package/template/.claude/skills/flydocs-figma/SKILL.md +377 -0
- package/template/.claude/skills/flydocs-figma/references/PROMPTING.md +108 -0
- package/template/.claude/skills/flydocs-figma/references/TROUBLESHOOTING.md +112 -0
- package/template/.claude/skills/flydocs-local/SKILL.md +103 -0
- package/template/.claude/skills/flydocs-local/cursor-rule.mdc +43 -0
- package/template/.claude/skills/flydocs-local/scripts/assign.py +20 -0
- package/template/.claude/skills/flydocs-local/scripts/comment.py +27 -0
- package/template/.claude/skills/flydocs-local/scripts/create_issue.py +44 -0
- package/template/.claude/skills/flydocs-local/scripts/estimate.py +37 -0
- package/template/.claude/skills/flydocs-local/scripts/flydocs_api.py +272 -0
- package/template/.claude/skills/flydocs-local/scripts/get_issue.py +20 -0
- package/template/.claude/skills/flydocs-local/scripts/link.py +41 -0
- package/template/.claude/skills/flydocs-local/scripts/list_issues.py +34 -0
- package/template/.claude/skills/flydocs-local/scripts/priority.py +37 -0
- package/template/.claude/skills/flydocs-local/scripts/project_update.py +67 -0
- package/template/.claude/skills/flydocs-local/scripts/status_summary.py +16 -0
- package/template/.claude/skills/flydocs-local/scripts/transition.py +24 -0
- package/template/.claude/skills/flydocs-local/scripts/update_description.py +35 -0
- package/template/.claude/skills/flydocs-local/scripts/update_issue.py +84 -0
- package/template/.claude/skills/flydocs-workflow/SKILL.md +85 -0
- package/template/.claude/skills/flydocs-workflow/cursor-rule.mdc +53 -0
- package/template/.claude/skills/flydocs-workflow/reference/comment-templates.md +131 -0
- package/template/.claude/skills/flydocs-workflow/reference/golden-rules.md +76 -0
- package/template/.claude/skills/flydocs-workflow/reference/priority-estimates.md +28 -0
- package/template/.claude/skills/flydocs-workflow/reference/status-workflow.md +50 -0
- package/template/.claude/skills/flydocs-workflow/session.md +128 -0
- package/template/.claude/skills/flydocs-workflow/stages/activate.md +46 -0
- package/template/.claude/skills/flydocs-workflow/stages/capture.md +50 -0
- package/template/.claude/skills/flydocs-workflow/stages/close.md +32 -0
- package/template/.claude/skills/flydocs-workflow/stages/implement.md +124 -0
- package/template/.claude/skills/flydocs-workflow/stages/refine.md +51 -0
- package/template/.claude/skills/flydocs-workflow/stages/review.md +86 -0
- package/template/.claude/skills/flydocs-workflow/stages/validate.md +90 -0
- package/template/.claude/skills/flydocs-workflow/templates/bug.md +95 -0
- package/template/.claude/skills/flydocs-workflow/templates/chore.md +75 -0
- package/template/.claude/skills/flydocs-workflow/templates/feature.md +93 -0
- package/template/.claude/skills/flydocs-workflow/templates/idea.md +84 -0
- package/template/.cursor/agents/implementation-agent.md +28 -0
- package/template/.cursor/agents/pm-agent.md +27 -0
- package/template/.cursor/agents/research-agent.md +23 -0
- package/template/.cursor/agents/review-agent.md +27 -0
- package/template/.cursor/hooks.json +29 -0
- package/template/.cursor/mcp.json +16 -0
- package/template/.env.example +44 -0
- package/template/.flydocs/config.json +104 -0
- package/template/.flydocs/hooks/auto-approve.py +71 -0
- package/template/.flydocs/hooks/post-edit.py +72 -0
- package/template/.flydocs/hooks/prefer-scripts.py +89 -0
- package/template/.flydocs/hooks/prompt-submit.py +277 -0
- package/template/.flydocs/scripts/generate_manifest.py +287 -0
- package/template/.flydocs/scripts/skill_manager.py +541 -0
- package/template/.flydocs/templates/README.md +46 -0
- package/template/.flydocs/templates/bug.md +166 -0
- package/template/.flydocs/templates/chore.md +110 -0
- package/template/.flydocs/templates/design-system/README.md +27 -0
- package/template/.flydocs/templates/design-system/component-patterns.md +92 -0
- package/template/.flydocs/templates/design-system/token-mapping.md +168 -0
- package/template/.flydocs/templates/feature.md +173 -0
- package/template/.flydocs/templates/idea.md +122 -0
- package/template/.flydocs/templates/instructions.md +228 -0
- package/template/.flydocs/templates/quick-capture.md +35 -0
- package/template/.flydocs/templates/scripts/check-design-system.template.mjs +179 -0
- package/template/.flydocs/version +1 -0
- package/template/AGENTS.md +95 -0
- package/template/CHANGELOG.md +271 -0
- package/template/flydocs/README.md +186 -0
- package/template/flydocs/context/project.md +51 -0
- package/template/flydocs/design-system/README.md +126 -0
- package/template/flydocs/design-system/component-patterns.md +173 -0
- package/template/flydocs/design-system/token-mapping.md +114 -0
- package/template/flydocs/knowledge/INDEX.md +100 -0
- package/template/flydocs/knowledge/README.md +62 -0
- package/template/flydocs/knowledge/product/personas.md +79 -0
- package/template/flydocs/knowledge/product/user-flows.md +88 -0
- package/template/manifest.json +221 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,2666 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/lib/constants.ts
|
|
13
|
+
import pc from "picocolors";
|
|
14
|
+
var CLI_VERSION, CLI_NAME, PACKAGE_NAME;
|
|
15
|
+
var init_constants = __esm({
|
|
16
|
+
"src/lib/constants.ts"() {
|
|
17
|
+
"use strict";
|
|
18
|
+
CLI_VERSION = "0.5.0-beta.0";
|
|
19
|
+
CLI_NAME = "flydocs";
|
|
20
|
+
PACKAGE_NAME = "@flydocs/cli";
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// src/lib/fs-ops.ts
|
|
25
|
+
import {
|
|
26
|
+
access,
|
|
27
|
+
cp,
|
|
28
|
+
mkdir,
|
|
29
|
+
rm,
|
|
30
|
+
copyFile as fsCopyFile
|
|
31
|
+
} from "fs/promises";
|
|
32
|
+
import { join } from "path";
|
|
33
|
+
async function pathExists(p) {
|
|
34
|
+
try {
|
|
35
|
+
await access(p);
|
|
36
|
+
return true;
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function ensureDirectories(targetDir, tier) {
|
|
42
|
+
const dirs = [
|
|
43
|
+
".flydocs",
|
|
44
|
+
".claude/skills",
|
|
45
|
+
".claude/agents",
|
|
46
|
+
".claude/commands",
|
|
47
|
+
".cursor/agents",
|
|
48
|
+
".cursor/commands",
|
|
49
|
+
".cursor/rules",
|
|
50
|
+
"flydocs/context",
|
|
51
|
+
"flydocs/knowledge/decisions",
|
|
52
|
+
"flydocs/knowledge/notes",
|
|
53
|
+
"flydocs/knowledge/product",
|
|
54
|
+
"flydocs/design-system"
|
|
55
|
+
];
|
|
56
|
+
if (tier === "local") {
|
|
57
|
+
dirs.push("flydocs/issues");
|
|
58
|
+
}
|
|
59
|
+
for (const dir of dirs) {
|
|
60
|
+
await mkdir(join(targetDir, dir), { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function replaceDirectory(src, dest) {
|
|
64
|
+
if (await pathExists(dest)) {
|
|
65
|
+
await rm(dest, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
await cp(src, dest, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
async function copyFile(src, dest) {
|
|
70
|
+
await fsCopyFile(src, dest);
|
|
71
|
+
}
|
|
72
|
+
async function copyFileIfNotExists(src, dest) {
|
|
73
|
+
if (await pathExists(dest)) {
|
|
74
|
+
return "preserved";
|
|
75
|
+
}
|
|
76
|
+
await fsCopyFile(src, dest);
|
|
77
|
+
return "created";
|
|
78
|
+
}
|
|
79
|
+
async function copyDirectoryContents(src, dest) {
|
|
80
|
+
if (await pathExists(dest)) {
|
|
81
|
+
await rm(dest, { recursive: true, force: true });
|
|
82
|
+
}
|
|
83
|
+
await mkdir(dest, { recursive: true });
|
|
84
|
+
await cp(src, dest, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
var init_fs_ops = __esm({
|
|
87
|
+
"src/lib/fs-ops.ts"() {
|
|
88
|
+
"use strict";
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// src/lib/template.ts
|
|
93
|
+
import { readFile } from "fs/promises";
|
|
94
|
+
import { dirname, join as join2, resolve } from "path";
|
|
95
|
+
import { fileURLToPath } from "url";
|
|
96
|
+
async function resolveTemplatePath(localSource) {
|
|
97
|
+
if (localSource) {
|
|
98
|
+
const localPath = resolve(process.cwd(), "template");
|
|
99
|
+
if (await pathExists(localPath)) {
|
|
100
|
+
return localPath;
|
|
101
|
+
}
|
|
102
|
+
const parentPath = resolve(process.cwd(), "..", "template");
|
|
103
|
+
if (await pathExists(parentPath)) {
|
|
104
|
+
return parentPath;
|
|
105
|
+
}
|
|
106
|
+
const thisFile2 = fileURLToPath(import.meta.url);
|
|
107
|
+
const packageRoot2 = dirname(dirname(thisFile2));
|
|
108
|
+
const bundledPath = join2(packageRoot2, "template");
|
|
109
|
+
if (await pathExists(bundledPath)) {
|
|
110
|
+
return bundledPath;
|
|
111
|
+
}
|
|
112
|
+
throw new Error(`Local template directory not found at ${localPath}`);
|
|
113
|
+
}
|
|
114
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
115
|
+
const distDir = dirname(thisFile);
|
|
116
|
+
const packageRoot = dirname(distDir);
|
|
117
|
+
const templatePath = join2(packageRoot, "template");
|
|
118
|
+
if (await pathExists(templatePath)) {
|
|
119
|
+
return templatePath;
|
|
120
|
+
}
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Template directory not found at ${templatePath}. Is the package installed correctly?`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
async function readTemplateVersion(templateDir) {
|
|
126
|
+
const versionFile = join2(templateDir, ".flydocs", "version");
|
|
127
|
+
const content = await readFile(versionFile, "utf-8");
|
|
128
|
+
return content.trim();
|
|
129
|
+
}
|
|
130
|
+
var init_template = __esm({
|
|
131
|
+
"src/lib/template.ts"() {
|
|
132
|
+
"use strict";
|
|
133
|
+
init_fs_ops();
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// src/lib/ui.ts
|
|
138
|
+
import pc2 from "picocolors";
|
|
139
|
+
function printStatus(message) {
|
|
140
|
+
console.log(`${pc2.green("\u2714")} ${message}`);
|
|
141
|
+
}
|
|
142
|
+
function printWarning(message) {
|
|
143
|
+
console.log(`${pc2.yellow("\u26A0")} ${message}`);
|
|
144
|
+
}
|
|
145
|
+
function printError(message) {
|
|
146
|
+
console.error(`${pc2.red("\u2716")} ${message}`);
|
|
147
|
+
}
|
|
148
|
+
function printInfo(message) {
|
|
149
|
+
console.log(`${pc2.cyan("\u2139")} ${message}`);
|
|
150
|
+
}
|
|
151
|
+
function printStub(command) {
|
|
152
|
+
printWarning(`${pc2.bold(command)} is not yet implemented in the Node CLI.`);
|
|
153
|
+
console.log(` Use ${pc2.cyan("bash install.sh")} for now.`);
|
|
154
|
+
}
|
|
155
|
+
function printBanner(version) {
|
|
156
|
+
const pink = pc2.magenta;
|
|
157
|
+
const purple = (t) => pc2.cyan(t);
|
|
158
|
+
const dim = pc2.dim;
|
|
159
|
+
const bold = pc2.bold;
|
|
160
|
+
console.log();
|
|
161
|
+
console.log(` ${bold(pink("\u2588\u2588\u2588\u2588\u2588\u2588"))}`);
|
|
162
|
+
console.log(
|
|
163
|
+
` ${bold(pink("\u2588\u2588"))} ${bold(purple("\u2588\u2588\u2588\u2588\u2588\u2588"))} ${bold(pink("Fly"))}${bold(purple("Docs"))} ${bold(pink("Core"))}`
|
|
164
|
+
);
|
|
165
|
+
console.log(
|
|
166
|
+
` ${bold(purple("\u2588\u2588\u2588\u2588\u2588\u2588"))} ${dim("Context-as-a-Service Platform Built for Engineering at Altitude")}`
|
|
167
|
+
);
|
|
168
|
+
console.log(` ${bold(purple("\u2588\u2588"))}`);
|
|
169
|
+
console.log(` ${bold(purple("\u2588\u2588"))} ${dim(`v${version}`)}`);
|
|
170
|
+
console.log();
|
|
171
|
+
}
|
|
172
|
+
function printCompletionBox(title, lines) {
|
|
173
|
+
const width = 59;
|
|
174
|
+
const topBot = "\u2550".repeat(width);
|
|
175
|
+
console.log();
|
|
176
|
+
console.log(`\u2554${topBot}\u2557`);
|
|
177
|
+
const padding = Math.max(0, Math.floor((width - title.length) / 2));
|
|
178
|
+
console.log(
|
|
179
|
+
`\u2551${" ".repeat(padding)}${title}${" ".repeat(width - padding - title.length)}\u2551`
|
|
180
|
+
);
|
|
181
|
+
console.log(`\u255A${topBot}\u255D`);
|
|
182
|
+
console.log();
|
|
183
|
+
for (const line of lines) {
|
|
184
|
+
console.log(` ${line}`);
|
|
185
|
+
}
|
|
186
|
+
console.log();
|
|
187
|
+
}
|
|
188
|
+
function printBetaCta() {
|
|
189
|
+
const dim = pc2.dim;
|
|
190
|
+
console.log(
|
|
191
|
+
dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")
|
|
192
|
+
);
|
|
193
|
+
console.log();
|
|
194
|
+
console.log(` ${pc2.bold("Join the FlyDocs Closed Beta")}`);
|
|
195
|
+
console.log(
|
|
196
|
+
` ${dim("Early access to cloud features, priority support, and more.")}`
|
|
197
|
+
);
|
|
198
|
+
console.log(
|
|
199
|
+
` ${pc2.cyan("https://www.flydocs.ai?utm_source=cli&utm_medium=install&utm_campaign=beta")}`
|
|
200
|
+
);
|
|
201
|
+
console.log();
|
|
202
|
+
}
|
|
203
|
+
var init_ui = __esm({
|
|
204
|
+
"src/lib/ui.ts"() {
|
|
205
|
+
"use strict";
|
|
206
|
+
init_constants();
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// src/lib/config.ts
|
|
211
|
+
import { readFile as readFile2, writeFile } from "fs/promises";
|
|
212
|
+
import { join as join3 } from "path";
|
|
213
|
+
async function readConfig(dir) {
|
|
214
|
+
const content = await readFile2(join3(dir, ".flydocs", "config.json"), "utf-8");
|
|
215
|
+
return JSON.parse(content);
|
|
216
|
+
}
|
|
217
|
+
async function writeConfig(dir, config) {
|
|
218
|
+
const content = JSON.stringify(config, null, 2) + "\n";
|
|
219
|
+
await writeFile(join3(dir, ".flydocs", "config.json"), content, "utf-8");
|
|
220
|
+
}
|
|
221
|
+
async function createFreshConfig(templateDir, version, tier) {
|
|
222
|
+
const content = await readFile2(
|
|
223
|
+
join3(templateDir, ".flydocs", "config.json"),
|
|
224
|
+
"utf-8"
|
|
225
|
+
);
|
|
226
|
+
const config = JSON.parse(content);
|
|
227
|
+
config.version = version;
|
|
228
|
+
config.tier = tier;
|
|
229
|
+
return config;
|
|
230
|
+
}
|
|
231
|
+
function extractPreservedValues(config) {
|
|
232
|
+
return {
|
|
233
|
+
tier: config.tier,
|
|
234
|
+
setupComplete: config.setupComplete ?? false,
|
|
235
|
+
providerTeamId: config.provider?.teamId ?? null,
|
|
236
|
+
workspace: config.workspace ?? {},
|
|
237
|
+
issueLabels: config.issueLabels ?? {},
|
|
238
|
+
statusMapping: config.statusMapping ?? {},
|
|
239
|
+
detectedStack: config.detectedStack ?? {},
|
|
240
|
+
mcp: config.mcp ?? {},
|
|
241
|
+
skills: config.skills ?? {},
|
|
242
|
+
designSystem: config.designSystem ?? null,
|
|
243
|
+
aiLabor: config.aiLabor ?? {}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
async function mergeConfig(templateDir, version, tierFlag, preserved) {
|
|
247
|
+
const content = await readFile2(
|
|
248
|
+
join3(templateDir, ".flydocs", "config.json"),
|
|
249
|
+
"utf-8"
|
|
250
|
+
);
|
|
251
|
+
const config = JSON.parse(content);
|
|
252
|
+
config.version = version;
|
|
253
|
+
config.tier = tierFlag ?? preserved.tier;
|
|
254
|
+
config.setupComplete = preserved.setupComplete;
|
|
255
|
+
if (preserved.providerTeamId !== null) {
|
|
256
|
+
if (!config.provider) {
|
|
257
|
+
config.provider = { type: "linear", teamId: null };
|
|
258
|
+
}
|
|
259
|
+
config.provider.teamId = preserved.providerTeamId;
|
|
260
|
+
}
|
|
261
|
+
if (Object.keys(preserved.workspace).length > 0) {
|
|
262
|
+
config.workspace = preserved.workspace;
|
|
263
|
+
}
|
|
264
|
+
if (Object.keys(preserved.issueLabels).length > 0) {
|
|
265
|
+
config.issueLabels = preserved.issueLabels;
|
|
266
|
+
}
|
|
267
|
+
if (Object.keys(preserved.statusMapping).length > 0) {
|
|
268
|
+
config.statusMapping = preserved.statusMapping;
|
|
269
|
+
}
|
|
270
|
+
if (Object.keys(preserved.detectedStack).length > 0) {
|
|
271
|
+
config.detectedStack = preserved.detectedStack;
|
|
272
|
+
}
|
|
273
|
+
if (Object.keys(preserved.mcp).length > 0) {
|
|
274
|
+
config.mcp = preserved.mcp;
|
|
275
|
+
}
|
|
276
|
+
if (Object.keys(preserved.skills).length > 0) {
|
|
277
|
+
config.skills = preserved.skills;
|
|
278
|
+
}
|
|
279
|
+
if (preserved.designSystem !== null) {
|
|
280
|
+
config.designSystem = preserved.designSystem;
|
|
281
|
+
}
|
|
282
|
+
if (Object.keys(preserved.aiLabor).length > 0) {
|
|
283
|
+
config.aiLabor = preserved.aiLabor;
|
|
284
|
+
}
|
|
285
|
+
return config;
|
|
286
|
+
}
|
|
287
|
+
var init_config = __esm({
|
|
288
|
+
"src/lib/config.ts"() {
|
|
289
|
+
"use strict";
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// src/lib/skills.ts
|
|
294
|
+
import { join as join4 } from "path";
|
|
295
|
+
async function installOwnedSkills(templateDir, targetDir, tier) {
|
|
296
|
+
const skillsDir = join4(targetDir, ".claude", "skills");
|
|
297
|
+
const templateSkillsDir = join4(templateDir, ".claude", "skills");
|
|
298
|
+
await replaceDirectory(
|
|
299
|
+
join4(templateSkillsDir, "flydocs-workflow"),
|
|
300
|
+
join4(skillsDir, "flydocs-workflow")
|
|
301
|
+
);
|
|
302
|
+
const activeMech = MECHANISM_SKILLS[tier];
|
|
303
|
+
const inactiveMech = tier === "local" ? MECHANISM_SKILLS.cloud : MECHANISM_SKILLS.local;
|
|
304
|
+
await replaceDirectory(
|
|
305
|
+
join4(templateSkillsDir, activeMech),
|
|
306
|
+
join4(skillsDir, activeMech)
|
|
307
|
+
);
|
|
308
|
+
const { rm: rm5 } = await import("fs/promises");
|
|
309
|
+
const inactivePath = join4(skillsDir, inactiveMech);
|
|
310
|
+
if (await pathExists(inactivePath)) {
|
|
311
|
+
await rm5(inactivePath, { recursive: true, force: true });
|
|
312
|
+
}
|
|
313
|
+
for (const skill of OWNED_SKILLS) {
|
|
314
|
+
if (skill === "flydocs-workflow") continue;
|
|
315
|
+
const src = join4(templateSkillsDir, skill);
|
|
316
|
+
if (await pathExists(src)) {
|
|
317
|
+
await replaceDirectory(src, join4(skillsDir, skill));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
for (const skill of SUPPORTING_SKILLS) {
|
|
321
|
+
const src = join4(templateSkillsDir, skill);
|
|
322
|
+
if (await pathExists(src)) {
|
|
323
|
+
await replaceDirectory(src, join4(skillsDir, skill));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const readmeSrc = join4(templateSkillsDir, "README.md");
|
|
327
|
+
if (await pathExists(readmeSrc)) {
|
|
328
|
+
await copyFile(readmeSrc, join4(skillsDir, "README.md"));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
async function replaceOwnedSkills(templateDir, targetDir, tier) {
|
|
332
|
+
const skillsDir = join4(targetDir, ".claude", "skills");
|
|
333
|
+
const templateSkillsDir = join4(templateDir, ".claude", "skills");
|
|
334
|
+
for (const skill of [...OWNED_SKILLS, ...SUPPORTING_SKILLS]) {
|
|
335
|
+
const src = join4(templateSkillsDir, skill);
|
|
336
|
+
if (await pathExists(src)) {
|
|
337
|
+
await replaceDirectory(src, join4(skillsDir, skill));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const { rm: rm5 } = await import("fs/promises");
|
|
341
|
+
const activeMech = MECHANISM_SKILLS[tier];
|
|
342
|
+
const inactiveMech = tier === "local" ? MECHANISM_SKILLS.cloud : MECHANISM_SKILLS.local;
|
|
343
|
+
const inactivePath = join4(skillsDir, inactiveMech);
|
|
344
|
+
if (await pathExists(inactivePath)) {
|
|
345
|
+
await rm5(inactivePath, { recursive: true, force: true });
|
|
346
|
+
}
|
|
347
|
+
await replaceDirectory(
|
|
348
|
+
join4(templateSkillsDir, activeMech),
|
|
349
|
+
join4(skillsDir, activeMech)
|
|
350
|
+
);
|
|
351
|
+
const readmeSrc = join4(templateSkillsDir, "README.md");
|
|
352
|
+
if (await pathExists(readmeSrc)) {
|
|
353
|
+
await copyFile(readmeSrc, join4(skillsDir, "README.md"));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
async function copyCursorRules(targetDir) {
|
|
357
|
+
const { mkdir: mkdir6 } = await import("fs/promises");
|
|
358
|
+
const rulesDir = join4(targetDir, ".cursor", "rules");
|
|
359
|
+
await mkdir6(rulesDir, { recursive: true });
|
|
360
|
+
const workflowRule = join4(
|
|
361
|
+
targetDir,
|
|
362
|
+
".claude",
|
|
363
|
+
"skills",
|
|
364
|
+
"flydocs-workflow",
|
|
365
|
+
"cursor-rule.mdc"
|
|
366
|
+
);
|
|
367
|
+
if (await pathExists(workflowRule)) {
|
|
368
|
+
await copyFile(workflowRule, join4(rulesDir, "flydocs-workflow.mdc"));
|
|
369
|
+
}
|
|
370
|
+
for (const mech of ["flydocs-local", "flydocs-cloud"]) {
|
|
371
|
+
const mechRule = join4(
|
|
372
|
+
targetDir,
|
|
373
|
+
".claude",
|
|
374
|
+
"skills",
|
|
375
|
+
mech,
|
|
376
|
+
"cursor-rule.mdc"
|
|
377
|
+
);
|
|
378
|
+
if (await pathExists(mechRule)) {
|
|
379
|
+
await copyFile(mechRule, join4(rulesDir, "flydocs-mechanism.mdc"));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
for (const skill of PREMIUM_CURSOR_RULES) {
|
|
383
|
+
const skillRule = join4(
|
|
384
|
+
targetDir,
|
|
385
|
+
".claude",
|
|
386
|
+
"skills",
|
|
387
|
+
skill,
|
|
388
|
+
"cursor-rule.mdc"
|
|
389
|
+
);
|
|
390
|
+
if (await pathExists(skillRule)) {
|
|
391
|
+
await copyFile(skillRule, join4(rulesDir, `${skill}.mdc`));
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
var OWNED_SKILLS, SUPPORTING_SKILLS, MECHANISM_SKILLS, PREMIUM_CURSOR_RULES;
|
|
396
|
+
var init_skills = __esm({
|
|
397
|
+
"src/lib/skills.ts"() {
|
|
398
|
+
"use strict";
|
|
399
|
+
init_fs_ops();
|
|
400
|
+
OWNED_SKILLS = [
|
|
401
|
+
"flydocs-workflow",
|
|
402
|
+
"flydocs-figma",
|
|
403
|
+
"flydocs-estimates",
|
|
404
|
+
"flydocs-context-graph"
|
|
405
|
+
];
|
|
406
|
+
SUPPORTING_SKILLS = [
|
|
407
|
+
"implementation-flow",
|
|
408
|
+
"review-workflow",
|
|
409
|
+
"spec-templates"
|
|
410
|
+
];
|
|
411
|
+
MECHANISM_SKILLS = {
|
|
412
|
+
local: "flydocs-local",
|
|
413
|
+
cloud: "flydocs-cloud"
|
|
414
|
+
};
|
|
415
|
+
PREMIUM_CURSOR_RULES = ["flydocs-figma", "flydocs-estimates"];
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// src/lib/stack.ts
|
|
420
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
421
|
+
import { join as join5 } from "path";
|
|
422
|
+
async function detectStack(targetDir) {
|
|
423
|
+
const detected = /* @__PURE__ */ new Set();
|
|
424
|
+
const pkgPath = join5(targetDir, "package.json");
|
|
425
|
+
let pkg;
|
|
426
|
+
try {
|
|
427
|
+
const content = await readFile3(pkgPath, "utf-8");
|
|
428
|
+
pkg = JSON.parse(content);
|
|
429
|
+
} catch {
|
|
430
|
+
}
|
|
431
|
+
if (pkg) {
|
|
432
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
433
|
+
if ("next" in allDeps) detected.add("nextjs");
|
|
434
|
+
if ("expo" in allDeps) detected.add("expo");
|
|
435
|
+
if ("react" in allDeps) detected.add("react");
|
|
436
|
+
if ("convex" in allDeps) detected.add("convex");
|
|
437
|
+
if ("prisma" in allDeps) detected.add("prisma");
|
|
438
|
+
if (Object.keys(allDeps).some((k) => k.startsWith("@clerk")))
|
|
439
|
+
detected.add("clerk");
|
|
440
|
+
if (Object.keys(allDeps).some((k) => k.startsWith("@workos")))
|
|
441
|
+
detected.add("workos");
|
|
442
|
+
if ("tailwindcss" in allDeps) detected.add("tailwind");
|
|
443
|
+
if ("vitest" in allDeps) detected.add("vitest");
|
|
444
|
+
if ("jest" in allDeps) detected.add("jest");
|
|
445
|
+
if (Object.keys(allDeps).some((k) => k.startsWith("@testing-library")))
|
|
446
|
+
detected.add("testing-library");
|
|
447
|
+
}
|
|
448
|
+
for (const f of ["convex/schema.ts", "convex/schema.js"]) {
|
|
449
|
+
if (await pathExists(join5(targetDir, f))) detected.add("convex");
|
|
450
|
+
}
|
|
451
|
+
for (const f of ["next.config.ts", "next.config.js", "next.config.mjs"]) {
|
|
452
|
+
if (await pathExists(join5(targetDir, f))) detected.add("nextjs");
|
|
453
|
+
}
|
|
454
|
+
const appJsonPath = join5(targetDir, "app.json");
|
|
455
|
+
if (await pathExists(appJsonPath)) {
|
|
456
|
+
try {
|
|
457
|
+
const appContent = await readFile3(appJsonPath, "utf-8");
|
|
458
|
+
if (appContent.includes('"expo"')) detected.add("expo");
|
|
459
|
+
} catch {
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (await pathExists(join5(targetDir, "tsconfig.json"))) {
|
|
463
|
+
detected.add("typescript");
|
|
464
|
+
}
|
|
465
|
+
const raw = Array.from(detected);
|
|
466
|
+
return {
|
|
467
|
+
raw,
|
|
468
|
+
frameworks: raw.filter((s) => ["nextjs", "react", "expo"].includes(s)),
|
|
469
|
+
database: raw.filter((s) => ["convex", "prisma"].includes(s)),
|
|
470
|
+
auth: raw.filter((s) => ["clerk", "workos"].includes(s)),
|
|
471
|
+
styling: raw.filter((s) => ["tailwind"].includes(s)),
|
|
472
|
+
language: raw.filter((s) => ["typescript"].includes(s)),
|
|
473
|
+
testing: raw.filter(
|
|
474
|
+
(s) => ["vitest", "jest", "testing-library"].includes(s)
|
|
475
|
+
)
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
function stackToDetectedConfig(stack) {
|
|
479
|
+
return {
|
|
480
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
481
|
+
frameworks: stack.frameworks,
|
|
482
|
+
database: stack.database,
|
|
483
|
+
auth: stack.auth,
|
|
484
|
+
styling: stack.styling,
|
|
485
|
+
language: stack.language,
|
|
486
|
+
testing: stack.testing
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
var init_stack = __esm({
|
|
490
|
+
"src/lib/stack.ts"() {
|
|
491
|
+
"use strict";
|
|
492
|
+
init_fs_ops();
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// src/lib/post-install.ts
|
|
497
|
+
import { execSync } from "child_process";
|
|
498
|
+
import { join as join6 } from "path";
|
|
499
|
+
async function runManifestGeneration(targetDir) {
|
|
500
|
+
const scriptPath = join6(
|
|
501
|
+
targetDir,
|
|
502
|
+
".flydocs",
|
|
503
|
+
"scripts",
|
|
504
|
+
"generate_manifest.py"
|
|
505
|
+
);
|
|
506
|
+
if (!await pathExists(scriptPath)) return;
|
|
507
|
+
try {
|
|
508
|
+
execSync(`python3 "${scriptPath}"`, {
|
|
509
|
+
cwd: targetDir,
|
|
510
|
+
stdio: "pipe"
|
|
511
|
+
});
|
|
512
|
+
printStatus("Skill manifest");
|
|
513
|
+
} catch {
|
|
514
|
+
printWarning("Skill manifest generation failed (non-fatal)");
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
async function runContextGraphBuild(targetDir) {
|
|
518
|
+
const scriptPath = join6(
|
|
519
|
+
targetDir,
|
|
520
|
+
".claude",
|
|
521
|
+
"skills",
|
|
522
|
+
"flydocs-context-graph",
|
|
523
|
+
"scripts",
|
|
524
|
+
"graph_build.py"
|
|
525
|
+
);
|
|
526
|
+
if (!await pathExists(scriptPath)) return;
|
|
527
|
+
try {
|
|
528
|
+
execSync(`python3 "${scriptPath}"`, {
|
|
529
|
+
cwd: targetDir,
|
|
530
|
+
stdio: "pipe"
|
|
531
|
+
});
|
|
532
|
+
printStatus("Context graph");
|
|
533
|
+
} catch {
|
|
534
|
+
printWarning("Context graph build failed (non-fatal)");
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
var init_post_install = __esm({
|
|
538
|
+
"src/lib/post-install.ts"() {
|
|
539
|
+
"use strict";
|
|
540
|
+
init_fs_ops();
|
|
541
|
+
init_ui();
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// src/lib/skill-manager.ts
|
|
546
|
+
import { readFile as readFile4, readdir, rm as rm2, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
547
|
+
import { join as join7 } from "path";
|
|
548
|
+
import pc3 from "picocolors";
|
|
549
|
+
function flushFrontmatterValue(mode, lines) {
|
|
550
|
+
switch (mode) {
|
|
551
|
+
case "scalar":
|
|
552
|
+
return lines[0] ?? "";
|
|
553
|
+
case "list":
|
|
554
|
+
return lines;
|
|
555
|
+
case "block":
|
|
556
|
+
return lines.join("\n").trim();
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function parseFrontmatter(text3) {
|
|
560
|
+
const match = text3.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
561
|
+
if (!match) return null;
|
|
562
|
+
const block = match[1];
|
|
563
|
+
const result = {};
|
|
564
|
+
let currentKey = null;
|
|
565
|
+
let currentMode = "scalar";
|
|
566
|
+
let currentLines = [];
|
|
567
|
+
for (const line of block.split("\n")) {
|
|
568
|
+
const keyMatch = line.match(/^(\w[\w-]*):\s*(.*)/);
|
|
569
|
+
if (keyMatch && !line.startsWith(" ")) {
|
|
570
|
+
if (currentKey !== null) {
|
|
571
|
+
result[currentKey] = flushFrontmatterValue(currentMode, currentLines);
|
|
572
|
+
}
|
|
573
|
+
currentKey = keyMatch[1];
|
|
574
|
+
const value = keyMatch[2].trim();
|
|
575
|
+
if (value === "|" || value === ">") {
|
|
576
|
+
currentMode = "block";
|
|
577
|
+
currentLines = [];
|
|
578
|
+
} else if (value === "") {
|
|
579
|
+
currentMode = "list";
|
|
580
|
+
currentLines = [];
|
|
581
|
+
} else {
|
|
582
|
+
currentMode = "scalar";
|
|
583
|
+
currentLines = [value.replace(/^["']|["']$/g, "")];
|
|
584
|
+
}
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
const stripped = line.trim();
|
|
588
|
+
if (currentKey !== null) {
|
|
589
|
+
if (currentMode === "list" && stripped.startsWith("- ")) {
|
|
590
|
+
currentLines.push(
|
|
591
|
+
stripped.slice(2).trim().replace(/^["']|["']$/g, "")
|
|
592
|
+
);
|
|
593
|
+
} else if (currentMode === "block") {
|
|
594
|
+
currentLines.push(line.replace(/^\s+/, ""));
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (currentKey !== null) {
|
|
599
|
+
result[currentKey] = flushFrontmatterValue(currentMode, currentLines);
|
|
600
|
+
}
|
|
601
|
+
return result;
|
|
602
|
+
}
|
|
603
|
+
function isPlatformSkill(name) {
|
|
604
|
+
return name.startsWith("flydocs-");
|
|
605
|
+
}
|
|
606
|
+
function parseSource(source) {
|
|
607
|
+
const urlMatch = source.match(
|
|
608
|
+
/^https?:\/\/github\.com\/([^/]+\/[^/]+)\/tree\/[^/]+\/skills\/([^/]+)\/?$/
|
|
609
|
+
);
|
|
610
|
+
if (urlMatch) {
|
|
611
|
+
return { repo: urlMatch[1], name: urlMatch[2] };
|
|
612
|
+
}
|
|
613
|
+
if (source.includes(":")) {
|
|
614
|
+
const colonIdx = source.lastIndexOf(":");
|
|
615
|
+
const repo = source.slice(0, colonIdx);
|
|
616
|
+
const name = source.slice(colonIdx + 1);
|
|
617
|
+
if (repo.includes("/")) {
|
|
618
|
+
return { repo, name };
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
const catalogEntry = COMMUNITY_CATALOG.find((s) => s.name === source);
|
|
622
|
+
if (catalogEntry) {
|
|
623
|
+
return { repo: catalogEntry.repo, name: catalogEntry.name };
|
|
624
|
+
}
|
|
625
|
+
throw new Error(
|
|
626
|
+
`Unknown skill: ${source}
|
|
627
|
+
Try: flydocs skills search <keyword>
|
|
628
|
+
Or use full reference: owner/repo:skill-name`
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
async function fetchGitHubJson(url) {
|
|
632
|
+
const controller = new AbortController();
|
|
633
|
+
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
634
|
+
try {
|
|
635
|
+
const response = await fetch(url, {
|
|
636
|
+
headers: { "User-Agent": "flydocs-cli" },
|
|
637
|
+
signal: controller.signal
|
|
638
|
+
});
|
|
639
|
+
if (response.status === 404) return null;
|
|
640
|
+
if (!response.ok) {
|
|
641
|
+
throw new Error(
|
|
642
|
+
`GitHub API returned ${response.status}: ${response.statusText}`
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
const data = await response.json();
|
|
646
|
+
if (Array.isArray(data)) {
|
|
647
|
+
return data;
|
|
648
|
+
}
|
|
649
|
+
return [data];
|
|
650
|
+
} finally {
|
|
651
|
+
clearTimeout(timeout);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
async function downloadFileContent(downloadUrl) {
|
|
655
|
+
const controller = new AbortController();
|
|
656
|
+
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
657
|
+
try {
|
|
658
|
+
const response = await fetch(downloadUrl, {
|
|
659
|
+
headers: { "User-Agent": "flydocs-cli" },
|
|
660
|
+
signal: controller.signal
|
|
661
|
+
});
|
|
662
|
+
if (!response.ok) {
|
|
663
|
+
throw new Error(`Download failed: ${response.status}`);
|
|
664
|
+
}
|
|
665
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
666
|
+
return Buffer.from(arrayBuffer);
|
|
667
|
+
} finally {
|
|
668
|
+
clearTimeout(timeout);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
async function downloadRecursive(url, destDir) {
|
|
672
|
+
const items = await fetchGitHubJson(url);
|
|
673
|
+
if (items === null) return false;
|
|
674
|
+
await mkdir2(destDir, { recursive: true });
|
|
675
|
+
for (const item of items) {
|
|
676
|
+
const destPath = join7(destDir, item.name);
|
|
677
|
+
if (item.type === "file") {
|
|
678
|
+
if (item.download_url) {
|
|
679
|
+
const content = await downloadFileContent(item.download_url);
|
|
680
|
+
await writeFile2(destPath, content);
|
|
681
|
+
}
|
|
682
|
+
} else if (item.type === "dir") {
|
|
683
|
+
await downloadRecursive(item.url, destPath);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return true;
|
|
687
|
+
}
|
|
688
|
+
async function downloadSkillTree(repo, skillName, targetDir) {
|
|
689
|
+
const apiUrl = `https://api.github.com/repos/${repo}/contents/skills/${skillName}`;
|
|
690
|
+
return downloadRecursive(apiUrl, targetDir);
|
|
691
|
+
}
|
|
692
|
+
async function listSkills(targetDir) {
|
|
693
|
+
const skillsDir = join7(targetDir, ".claude", "skills");
|
|
694
|
+
const platform = [];
|
|
695
|
+
const community = [];
|
|
696
|
+
if (!await pathExists(skillsDir)) {
|
|
697
|
+
return { platform, community };
|
|
698
|
+
}
|
|
699
|
+
let entries;
|
|
700
|
+
try {
|
|
701
|
+
entries = await readdir(skillsDir);
|
|
702
|
+
} catch {
|
|
703
|
+
return { platform, community };
|
|
704
|
+
}
|
|
705
|
+
for (const entry of entries.sort()) {
|
|
706
|
+
const skillFile = join7(skillsDir, entry, "SKILL.md");
|
|
707
|
+
if (!await pathExists(skillFile)) continue;
|
|
708
|
+
let content;
|
|
709
|
+
try {
|
|
710
|
+
content = await readFile4(skillFile, "utf-8");
|
|
711
|
+
} catch {
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
const fm = parseFrontmatter(content);
|
|
715
|
+
const name = fm !== null && typeof fm["name"] === "string" ? fm["name"] : entry;
|
|
716
|
+
let triggerCount = 0;
|
|
717
|
+
if (fm !== null) {
|
|
718
|
+
const triggers = fm["triggers"];
|
|
719
|
+
if (Array.isArray(triggers)) {
|
|
720
|
+
triggerCount = triggers.length;
|
|
721
|
+
} else if (typeof triggers === "string") {
|
|
722
|
+
triggerCount = triggers.split(",").filter((t) => t.trim().length > 0).length;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
const info = {
|
|
726
|
+
name,
|
|
727
|
+
dir: entry,
|
|
728
|
+
type: isPlatformSkill(entry) ? "platform" : "community",
|
|
729
|
+
triggers: triggerCount
|
|
730
|
+
};
|
|
731
|
+
if (info.type === "platform") {
|
|
732
|
+
platform.push(info);
|
|
733
|
+
} else {
|
|
734
|
+
community.push(info);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return { platform, community };
|
|
738
|
+
}
|
|
739
|
+
function searchCatalog(keyword) {
|
|
740
|
+
const lower = keyword.toLowerCase();
|
|
741
|
+
return COMMUNITY_CATALOG.filter((skill) => {
|
|
742
|
+
const searchable = `${skill.name} ${skill.description} ${skill.tags.join(" ")}`.toLowerCase();
|
|
743
|
+
return searchable.includes(lower);
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
async function addSkill(targetDir, source) {
|
|
747
|
+
if (isPlatformSkill(source)) {
|
|
748
|
+
printError(`Cannot install platform skill '${source}'.`);
|
|
749
|
+
console.log(" Platform skills (flydocs-*) are managed by the installer.");
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
const { repo, name: skillName } = parseSource(source);
|
|
753
|
+
if (isPlatformSkill(skillName)) {
|
|
754
|
+
printError(`Cannot install platform skill '${skillName}'.`);
|
|
755
|
+
console.log(" Platform skills (flydocs-*) are managed by the installer.");
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
const skillsDir = join7(targetDir, ".claude", "skills", skillName);
|
|
759
|
+
if (await pathExists(skillsDir)) {
|
|
760
|
+
printWarning(`Skill '${skillName}' is already installed.`);
|
|
761
|
+
console.log(` Remove first: flydocs skills remove ${skillName}`);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
console.log();
|
|
765
|
+
console.log(
|
|
766
|
+
`${pc3.blue("->")} Installing ${pc3.cyan(skillName)} from ${repo}...`
|
|
767
|
+
);
|
|
768
|
+
console.log();
|
|
769
|
+
let success;
|
|
770
|
+
try {
|
|
771
|
+
success = await downloadSkillTree(repo, skillName, skillsDir);
|
|
772
|
+
} catch (err) {
|
|
773
|
+
if (await pathExists(skillsDir)) {
|
|
774
|
+
await rm2(skillsDir, { recursive: true, force: true });
|
|
775
|
+
}
|
|
776
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
777
|
+
printError(`Download failed: ${message}`);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
if (!success) {
|
|
781
|
+
if (await pathExists(skillsDir)) {
|
|
782
|
+
await rm2(skillsDir, { recursive: true, force: true });
|
|
783
|
+
}
|
|
784
|
+
printError(`Skill not found at ${repo}/skills/${skillName}`);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
const skillMdPath = join7(skillsDir, "SKILL.md");
|
|
788
|
+
if (!await pathExists(skillMdPath)) {
|
|
789
|
+
await rm2(skillsDir, { recursive: true, force: true });
|
|
790
|
+
printError("Invalid skill: SKILL.md not found.");
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
const skillMdContent = await readFile4(skillMdPath, "utf-8");
|
|
794
|
+
const fm = parseFrontmatter(skillMdContent);
|
|
795
|
+
if (fm === null || !fm["name"] || !fm["description"]) {
|
|
796
|
+
await rm2(skillsDir, { recursive: true, force: true });
|
|
797
|
+
printError(
|
|
798
|
+
"Invalid skill: SKILL.md missing required frontmatter (name, description)."
|
|
799
|
+
);
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
if (!fm["triggers"]) {
|
|
803
|
+
printWarning(
|
|
804
|
+
"SKILL.md has no triggers -- skill won't appear in manifest index."
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
printStatus("Downloaded skill files");
|
|
808
|
+
const cursorRuleSrc = join7(skillsDir, "cursor-rule.mdc");
|
|
809
|
+
if (await pathExists(cursorRuleSrc)) {
|
|
810
|
+
const cursorRulesDir = join7(targetDir, ".cursor", "rules");
|
|
811
|
+
await mkdir2(cursorRulesDir, { recursive: true });
|
|
812
|
+
const cursorRuleDest = join7(cursorRulesDir, `${skillName}.mdc`);
|
|
813
|
+
await copyFile(cursorRuleSrc, cursorRuleDest);
|
|
814
|
+
printStatus("Installed cursor rule");
|
|
815
|
+
}
|
|
816
|
+
const config = await readConfig(targetDir);
|
|
817
|
+
const installed = config.skills?.installed ?? [];
|
|
818
|
+
const entry = `${repo}/${skillName}`;
|
|
819
|
+
if (!installed.includes(entry)) {
|
|
820
|
+
installed.push(entry);
|
|
821
|
+
if (!config.skills) {
|
|
822
|
+
config.skills = { installed: [], custom: [] };
|
|
823
|
+
}
|
|
824
|
+
config.skills.installed = installed;
|
|
825
|
+
await writeConfig(targetDir, config);
|
|
826
|
+
printStatus("Updated config.json");
|
|
827
|
+
}
|
|
828
|
+
await runManifestGeneration(targetDir);
|
|
829
|
+
console.log();
|
|
830
|
+
printStatus(`Installed ${pc3.cyan(skillName)}`);
|
|
831
|
+
console.log();
|
|
832
|
+
}
|
|
833
|
+
async function removeSkill(targetDir, name) {
|
|
834
|
+
if (isPlatformSkill(name)) {
|
|
835
|
+
printError(`Cannot remove platform skill '${name}'.`);
|
|
836
|
+
console.log(" Platform skills (flydocs-*) are managed by the installer.");
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const skillDir = join7(targetDir, ".claude", "skills", name);
|
|
840
|
+
if (!await pathExists(skillDir)) {
|
|
841
|
+
printError(`Skill '${name}' is not installed.`);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
await rm2(skillDir, { recursive: true, force: true });
|
|
845
|
+
printStatus("Removed skill directory");
|
|
846
|
+
const cursorRule = join7(targetDir, ".cursor", "rules", `${name}.mdc`);
|
|
847
|
+
if (await pathExists(cursorRule)) {
|
|
848
|
+
await rm2(cursorRule, { force: true });
|
|
849
|
+
printStatus("Removed cursor rule");
|
|
850
|
+
}
|
|
851
|
+
const config = await readConfig(targetDir);
|
|
852
|
+
const installed = config.skills?.installed ?? [];
|
|
853
|
+
config.skills.installed = installed.filter((s) => !s.endsWith(`/${name}`));
|
|
854
|
+
await writeConfig(targetDir, config);
|
|
855
|
+
printStatus("Updated config.json");
|
|
856
|
+
await runManifestGeneration(targetDir);
|
|
857
|
+
console.log();
|
|
858
|
+
printStatus(`Removed ${pc3.cyan(name)}`);
|
|
859
|
+
console.log();
|
|
860
|
+
}
|
|
861
|
+
var COMMUNITY_CATALOG;
|
|
862
|
+
var init_skill_manager = __esm({
|
|
863
|
+
"src/lib/skill-manager.ts"() {
|
|
864
|
+
"use strict";
|
|
865
|
+
init_config();
|
|
866
|
+
init_fs_ops();
|
|
867
|
+
init_ui();
|
|
868
|
+
init_post_install();
|
|
869
|
+
COMMUNITY_CATALOG = [
|
|
870
|
+
{
|
|
871
|
+
name: "react-best-practices",
|
|
872
|
+
repo: "vercel-labs/agent-skills",
|
|
873
|
+
description: "React/Next.js performance optimization (40+ rules from Vercel)",
|
|
874
|
+
tags: ["react", "nextjs", "performance", "components"]
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
name: "web-design-guidelines",
|
|
878
|
+
repo: "vercel-labs/agent-skills",
|
|
879
|
+
description: "UI/UX accessibility and design patterns (100+ rules)",
|
|
880
|
+
tags: ["design", "accessibility", "ux", "ui", "nextjs", "web"]
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
name: "convex-best-practices",
|
|
884
|
+
repo: "waynesutton/convexskills",
|
|
885
|
+
description: "Convex database patterns and best practices",
|
|
886
|
+
tags: ["convex", "database", "backend"]
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
name: "convex-functions",
|
|
890
|
+
repo: "waynesutton/convexskills",
|
|
891
|
+
description: "Convex query/mutation/action patterns",
|
|
892
|
+
tags: ["convex", "functions", "query", "mutation"]
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
name: "tailwind-v4",
|
|
896
|
+
repo: "blencorp/claude-code-kit",
|
|
897
|
+
description: "Tailwind CSS v4 patterns and configuration",
|
|
898
|
+
tags: ["tailwind", "css", "styling"]
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
name: "clerk-auth",
|
|
902
|
+
repo: "blencorp/claude-code-kit",
|
|
903
|
+
description: "Clerk authentication patterns",
|
|
904
|
+
tags: ["clerk", "auth", "authentication"]
|
|
905
|
+
}
|
|
906
|
+
];
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
// src/lib/community-skills.ts
|
|
911
|
+
import { multiselect, isCancel, cancel } from "@clack/prompts";
|
|
912
|
+
import pc4 from "picocolors";
|
|
913
|
+
function suggestSkills(stack) {
|
|
914
|
+
const seen = /* @__PURE__ */ new Set();
|
|
915
|
+
const suggested = [];
|
|
916
|
+
for (const skill of COMMUNITY_SKILLS_MAP) {
|
|
917
|
+
if (stack.raw.includes(skill.stackTrigger)) {
|
|
918
|
+
const key = `${skill.repo}:${skill.name}`;
|
|
919
|
+
if (!seen.has(key)) {
|
|
920
|
+
seen.add(key);
|
|
921
|
+
suggested.push(skill);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return suggested;
|
|
926
|
+
}
|
|
927
|
+
async function promptCommunitySkills(targetDir, stack, autoYes) {
|
|
928
|
+
const suggestions = suggestSkills(stack);
|
|
929
|
+
if (suggestions.length === 0) {
|
|
930
|
+
printInfo("No community skills suggested for detected stack.");
|
|
931
|
+
console.log(` Browse more at: ${pc4.cyan("https://skills.sh/")}`);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
console.log();
|
|
935
|
+
console.log(` ${pc4.bold(pc4.yellow("Community Skills (Recommended)"))}`);
|
|
936
|
+
console.log();
|
|
937
|
+
console.log(` Based on your stack: ${pc4.cyan(stack.raw.join(", "))}`);
|
|
938
|
+
console.log();
|
|
939
|
+
console.log(" FlyDocs bundles workflow skills. Community skills add");
|
|
940
|
+
console.log(" stack-specific patterns from the Agent Skills ecosystem.");
|
|
941
|
+
console.log();
|
|
942
|
+
let selected;
|
|
943
|
+
if (autoYes) {
|
|
944
|
+
selected = suggestions;
|
|
945
|
+
console.log(" Auto-accepting all (--yes)");
|
|
946
|
+
} else {
|
|
947
|
+
const options = suggestions.map((s) => ({
|
|
948
|
+
value: s,
|
|
949
|
+
label: s.name,
|
|
950
|
+
hint: s.description
|
|
951
|
+
}));
|
|
952
|
+
const result = await multiselect({
|
|
953
|
+
message: "Select community skills to install",
|
|
954
|
+
options,
|
|
955
|
+
required: false
|
|
956
|
+
});
|
|
957
|
+
if (isCancel(result)) {
|
|
958
|
+
cancel("Skipped community skills");
|
|
959
|
+
selected = [];
|
|
960
|
+
} else {
|
|
961
|
+
selected = result;
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
if (selected.length === 0) {
|
|
965
|
+
printInfo(
|
|
966
|
+
"Skipped community skills. Install later: flydocs skills add <name>"
|
|
967
|
+
);
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
console.log();
|
|
971
|
+
console.log(` Installing ${selected.length} community skill(s)...`);
|
|
972
|
+
console.log();
|
|
973
|
+
let successCount = 0;
|
|
974
|
+
for (const skill of selected) {
|
|
975
|
+
try {
|
|
976
|
+
await addSkill(targetDir, `${skill.repo}:${skill.name}`);
|
|
977
|
+
successCount++;
|
|
978
|
+
} catch {
|
|
979
|
+
printError(`${skill.name} (failed)`);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
console.log();
|
|
983
|
+
printStatus(`${successCount} community skill(s) installed`);
|
|
984
|
+
console.log(` Manage skills: flydocs skills list`);
|
|
985
|
+
console.log(` Browse more at: ${pc4.cyan("https://skills.sh/")}`);
|
|
986
|
+
}
|
|
987
|
+
var COMMUNITY_SKILLS_MAP;
|
|
988
|
+
var init_community_skills = __esm({
|
|
989
|
+
"src/lib/community-skills.ts"() {
|
|
990
|
+
"use strict";
|
|
991
|
+
init_ui();
|
|
992
|
+
init_skill_manager();
|
|
993
|
+
COMMUNITY_SKILLS_MAP = [
|
|
994
|
+
{
|
|
995
|
+
stackTrigger: "nextjs",
|
|
996
|
+
repo: "vercel-labs/agent-skills",
|
|
997
|
+
name: "react-best-practices",
|
|
998
|
+
description: "React/Next.js performance optimization (40+ rules from Vercel)"
|
|
999
|
+
},
|
|
1000
|
+
{
|
|
1001
|
+
stackTrigger: "nextjs",
|
|
1002
|
+
repo: "vercel-labs/agent-skills",
|
|
1003
|
+
name: "web-design-guidelines",
|
|
1004
|
+
description: "UI/UX accessibility and design patterns (100+ rules)"
|
|
1005
|
+
},
|
|
1006
|
+
{
|
|
1007
|
+
stackTrigger: "react",
|
|
1008
|
+
repo: "vercel-labs/agent-skills",
|
|
1009
|
+
name: "react-best-practices",
|
|
1010
|
+
description: "React performance optimization (40+ rules from Vercel)"
|
|
1011
|
+
},
|
|
1012
|
+
{
|
|
1013
|
+
stackTrigger: "convex",
|
|
1014
|
+
repo: "waynesutton/convexskills",
|
|
1015
|
+
name: "convex-best-practices",
|
|
1016
|
+
description: "Convex database patterns and best practices"
|
|
1017
|
+
},
|
|
1018
|
+
{
|
|
1019
|
+
stackTrigger: "convex",
|
|
1020
|
+
repo: "waynesutton/convexskills",
|
|
1021
|
+
name: "convex-functions",
|
|
1022
|
+
description: "Convex query/mutation/action patterns"
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
stackTrigger: "tailwind",
|
|
1026
|
+
repo: "blencorp/claude-code-kit",
|
|
1027
|
+
name: "tailwind-v4",
|
|
1028
|
+
description: "Tailwind CSS v4 patterns and configuration"
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
stackTrigger: "clerk",
|
|
1032
|
+
repo: "blencorp/claude-code-kit",
|
|
1033
|
+
name: "clerk-auth",
|
|
1034
|
+
description: "Clerk authentication patterns"
|
|
1035
|
+
}
|
|
1036
|
+
];
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
// src/lib/deprecated.ts
|
|
1041
|
+
import { readdir as readdir2, mkdir as mkdir3, rename, rm as rm3 } from "fs/promises";
|
|
1042
|
+
import { join as join8 } from "path";
|
|
1043
|
+
import { confirm, isCancel as isCancel2 } from "@clack/prompts";
|
|
1044
|
+
async function scanDeprecated(targetDir) {
|
|
1045
|
+
const found = [];
|
|
1046
|
+
for (const item of [...DEPRECATED_DIRS, ...DEPRECATED_FILES]) {
|
|
1047
|
+
if (await pathExists(join8(targetDir, item))) {
|
|
1048
|
+
found.push(item);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
for (const skill of DEPRECATED_SKILLS) {
|
|
1052
|
+
const p = join8(targetDir, ".claude", "skills", skill);
|
|
1053
|
+
if (await pathExists(p)) {
|
|
1054
|
+
found.push(`.claude/skills/${skill}`);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
for (const dir of DEPRECATED_RULES_DIR) {
|
|
1058
|
+
if (await pathExists(join8(targetDir, dir))) {
|
|
1059
|
+
found.push(dir);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
for (const hook of DEPRECATED_HOOKS) {
|
|
1063
|
+
const p = join8(targetDir, ".flydocs", "hooks", hook);
|
|
1064
|
+
if (await pathExists(p)) {
|
|
1065
|
+
found.push(`.flydocs/hooks/${hook}`);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
for (const rule of DEPRECATED_CURSOR_RULES) {
|
|
1069
|
+
const p = join8(targetDir, ".cursor", "rules", rule);
|
|
1070
|
+
if (await pathExists(p)) {
|
|
1071
|
+
found.push(`.cursor/rules/${rule}`);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
for (const dir of DEPRECATED_CURSOR_RULE_DIRS) {
|
|
1075
|
+
const p = join8(targetDir, ".cursor", "rules", dir);
|
|
1076
|
+
if (await pathExists(p)) {
|
|
1077
|
+
found.push(`.cursor/rules/${dir}`);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
for (const cmd of DEPRECATED_COMMANDS) {
|
|
1081
|
+
for (const prefix of [".cursor/commands", ".claude/commands"]) {
|
|
1082
|
+
const p = join8(targetDir, prefix, cmd);
|
|
1083
|
+
if (await pathExists(p)) {
|
|
1084
|
+
found.push(`${prefix}/${cmd}`);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
return found;
|
|
1089
|
+
}
|
|
1090
|
+
async function handleLegacyContext(targetDir) {
|
|
1091
|
+
const contextDir = join8(targetDir, "flydocs", "context");
|
|
1092
|
+
const legacyDir = join8(contextDir, "legacy");
|
|
1093
|
+
const oldFiles = ["overview.md", "stack.md", "standards.md"];
|
|
1094
|
+
let hasOld = false;
|
|
1095
|
+
for (const f of oldFiles) {
|
|
1096
|
+
if (await pathExists(join8(contextDir, f))) {
|
|
1097
|
+
hasOld = true;
|
|
1098
|
+
break;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
if (hasOld) {
|
|
1102
|
+
await mkdir3(legacyDir, { recursive: true });
|
|
1103
|
+
for (const f of oldFiles) {
|
|
1104
|
+
const src = join8(contextDir, f);
|
|
1105
|
+
if (await pathExists(src)) {
|
|
1106
|
+
await rename(src, join8(legacyDir, f));
|
|
1107
|
+
printStatus(`Moved flydocs/context/${f} \u2192 legacy/`);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
printInfo(
|
|
1111
|
+
"Run /flydocs-setup to consolidate legacy context into project.md"
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
async function checkLegacyFolder(targetDir) {
|
|
1116
|
+
const legacyDir = join8(targetDir, "flydocs", "context", "legacy");
|
|
1117
|
+
if (!await pathExists(legacyDir)) return null;
|
|
1118
|
+
try {
|
|
1119
|
+
const entries = await readdir2(legacyDir);
|
|
1120
|
+
if (entries.length > 0) {
|
|
1121
|
+
return "flydocs/context/legacy";
|
|
1122
|
+
}
|
|
1123
|
+
} catch {
|
|
1124
|
+
}
|
|
1125
|
+
return null;
|
|
1126
|
+
}
|
|
1127
|
+
async function promptCleanup(targetDir, paths) {
|
|
1128
|
+
if (paths.length === 0) {
|
|
1129
|
+
printStatus("No deprecated files found");
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
console.log();
|
|
1133
|
+
printWarning("Found deprecated files/directories:");
|
|
1134
|
+
for (const p of paths) {
|
|
1135
|
+
console.log(` - ${p}`);
|
|
1136
|
+
}
|
|
1137
|
+
console.log();
|
|
1138
|
+
const shouldDelete = await confirm({
|
|
1139
|
+
message: "Delete deprecated files?"
|
|
1140
|
+
});
|
|
1141
|
+
if (isCancel2(shouldDelete) || !shouldDelete) {
|
|
1142
|
+
printInfo("Keeping deprecated files");
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
for (const p of paths) {
|
|
1146
|
+
await rm3(join8(targetDir, p), { recursive: true, force: true });
|
|
1147
|
+
printStatus(`Deleted: ${p}`);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
var DEPRECATED_DIRS, DEPRECATED_FILES, DEPRECATED_SKILLS, DEPRECATED_RULES_DIR, DEPRECATED_HOOKS, DEPRECATED_CURSOR_RULES, DEPRECATED_CURSOR_RULE_DIRS, DEPRECATED_COMMANDS;
|
|
1151
|
+
var init_deprecated = __esm({
|
|
1152
|
+
"src/lib/deprecated.ts"() {
|
|
1153
|
+
"use strict";
|
|
1154
|
+
init_fs_ops();
|
|
1155
|
+
init_ui();
|
|
1156
|
+
DEPRECATED_DIRS = [".docflow", "docflow"];
|
|
1157
|
+
DEPRECATED_FILES = ["AGENTS.md.bak"];
|
|
1158
|
+
DEPRECATED_SKILLS = [
|
|
1159
|
+
"linear-workflow",
|
|
1160
|
+
"session-workflow",
|
|
1161
|
+
"ai-labor-estimate",
|
|
1162
|
+
"component-workflow",
|
|
1163
|
+
"figma-mcp",
|
|
1164
|
+
"implementation-flow",
|
|
1165
|
+
"review-workflow",
|
|
1166
|
+
"spec-templates"
|
|
1167
|
+
];
|
|
1168
|
+
DEPRECATED_RULES_DIR = [".flydocs/rules"];
|
|
1169
|
+
DEPRECATED_HOOKS = ["linear-auto-approve.py", "session-end.py"];
|
|
1170
|
+
DEPRECATED_CURSOR_RULES = [
|
|
1171
|
+
"designer-agent.mdc",
|
|
1172
|
+
"docflow-core.mdc",
|
|
1173
|
+
"figma-integration.mdc",
|
|
1174
|
+
"flydocs-core.mdc",
|
|
1175
|
+
"impl-workflow.mdc",
|
|
1176
|
+
"implementation-agent.mdc",
|
|
1177
|
+
"linear-integration.mdc",
|
|
1178
|
+
"pm-agent.mdc",
|
|
1179
|
+
"pm-workflow.mdc",
|
|
1180
|
+
"qe-agent.mdc",
|
|
1181
|
+
"qe-workflow.mdc",
|
|
1182
|
+
"session-awareness.mdc",
|
|
1183
|
+
"templates.mdc"
|
|
1184
|
+
];
|
|
1185
|
+
DEPRECATED_CURSOR_RULE_DIRS = ["designer-agent", "docflow-core"];
|
|
1186
|
+
DEPRECATED_COMMANDS = [
|
|
1187
|
+
"docflow-setup.md",
|
|
1188
|
+
"docflow-update.md",
|
|
1189
|
+
"sync-project.md",
|
|
1190
|
+
"design-setup.md"
|
|
1191
|
+
];
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
// src/lib/gitignore.ts
|
|
1196
|
+
import { readFile as readFile5, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
1197
|
+
import { join as join9 } from "path";
|
|
1198
|
+
async function ensureGitignore(targetDir) {
|
|
1199
|
+
const gitignorePath = join9(targetDir, ".gitignore");
|
|
1200
|
+
if (await pathExists(gitignorePath)) {
|
|
1201
|
+
const content = await readFile5(gitignorePath, "utf-8");
|
|
1202
|
+
if (!content.includes("# FlyDocs")) {
|
|
1203
|
+
const section = "\n# FlyDocs\n" + FLYDOCS_GITIGNORE_ENTRIES.join("\n") + "\n";
|
|
1204
|
+
await appendFile(gitignorePath, section, "utf-8");
|
|
1205
|
+
printStatus("Added FlyDocs entries to .gitignore");
|
|
1206
|
+
}
|
|
1207
|
+
} else {
|
|
1208
|
+
await writeFile3(gitignorePath, FULL_GITIGNORE_TEMPLATE, "utf-8");
|
|
1209
|
+
printStatus(".gitignore (new)");
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
async function migrateGitignore(targetDir) {
|
|
1213
|
+
const gitignorePath = join9(targetDir, ".gitignore");
|
|
1214
|
+
if (!await pathExists(gitignorePath)) return;
|
|
1215
|
+
const content = await readFile5(gitignorePath, "utf-8");
|
|
1216
|
+
if (!content.includes("flydocs/context/graph.json")) {
|
|
1217
|
+
if (content.includes("# FlyDocs")) {
|
|
1218
|
+
const lines = content.split("\n");
|
|
1219
|
+
const flyDocsIdx = lines.findIndex((l) => l.trim() === "# FlyDocs");
|
|
1220
|
+
if (flyDocsIdx !== -1) {
|
|
1221
|
+
let insertIdx = flyDocsIdx + 1;
|
|
1222
|
+
while (insertIdx < lines.length && lines[insertIdx].trim() !== "" && !lines[insertIdx].startsWith("#")) {
|
|
1223
|
+
insertIdx++;
|
|
1224
|
+
}
|
|
1225
|
+
lines.splice(insertIdx, 0, "flydocs/context/graph.json");
|
|
1226
|
+
await writeFile3(gitignorePath, lines.join("\n"), "utf-8");
|
|
1227
|
+
} else {
|
|
1228
|
+
await appendFile(
|
|
1229
|
+
gitignorePath,
|
|
1230
|
+
"\nflydocs/context/graph.json\n",
|
|
1231
|
+
"utf-8"
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
} else {
|
|
1235
|
+
await appendFile(
|
|
1236
|
+
gitignorePath,
|
|
1237
|
+
"\nflydocs/context/graph.json\n",
|
|
1238
|
+
"utf-8"
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
printStatus("Added flydocs/context/graph.json to .gitignore");
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
var FLYDOCS_GITIGNORE_ENTRIES, FULL_GITIGNORE_TEMPLATE;
|
|
1245
|
+
var init_gitignore = __esm({
|
|
1246
|
+
"src/lib/gitignore.ts"() {
|
|
1247
|
+
"use strict";
|
|
1248
|
+
init_fs_ops();
|
|
1249
|
+
init_ui();
|
|
1250
|
+
FLYDOCS_GITIGNORE_ENTRIES = [
|
|
1251
|
+
".env",
|
|
1252
|
+
".env.local",
|
|
1253
|
+
".flydocs/session.json",
|
|
1254
|
+
".flydocs/logs/",
|
|
1255
|
+
".flydocs/backup-*/",
|
|
1256
|
+
"flydocs/context/graph.json"
|
|
1257
|
+
];
|
|
1258
|
+
FULL_GITIGNORE_TEMPLATE = `# Environment
|
|
1259
|
+
.env
|
|
1260
|
+
.env.local
|
|
1261
|
+
.env.*.local
|
|
1262
|
+
|
|
1263
|
+
# FlyDocs
|
|
1264
|
+
.flydocs/session.json
|
|
1265
|
+
.flydocs/logs/
|
|
1266
|
+
.flydocs/backup-*/
|
|
1267
|
+
flydocs/context/graph.json
|
|
1268
|
+
|
|
1269
|
+
# Dependencies
|
|
1270
|
+
node_modules/
|
|
1271
|
+
|
|
1272
|
+
# Build
|
|
1273
|
+
dist/
|
|
1274
|
+
build/
|
|
1275
|
+
.next/
|
|
1276
|
+
out/
|
|
1277
|
+
|
|
1278
|
+
# IDE
|
|
1279
|
+
.idea/
|
|
1280
|
+
*.swp
|
|
1281
|
+
*.swo
|
|
1282
|
+
.DS_Store
|
|
1283
|
+
|
|
1284
|
+
# Logs
|
|
1285
|
+
*.log
|
|
1286
|
+
|
|
1287
|
+
# Python
|
|
1288
|
+
__pycache__/
|
|
1289
|
+
*.pyc
|
|
1290
|
+
`;
|
|
1291
|
+
}
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
// src/lib/version.ts
|
|
1295
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
1296
|
+
function compareVersions(v1, v2) {
|
|
1297
|
+
const parts1 = v1.split(".").map(Number);
|
|
1298
|
+
const parts2 = v2.split(".").map(Number);
|
|
1299
|
+
const len = Math.max(parts1.length, parts2.length);
|
|
1300
|
+
for (let i = 0; i < len; i++) {
|
|
1301
|
+
const a = parts1[i] ?? 0;
|
|
1302
|
+
const b = parts2[i] ?? 0;
|
|
1303
|
+
if (a < b) return "older";
|
|
1304
|
+
if (a > b) return "newer";
|
|
1305
|
+
}
|
|
1306
|
+
return "equal";
|
|
1307
|
+
}
|
|
1308
|
+
async function getWhatsNew(changelogPath, fromVersion, toVersion) {
|
|
1309
|
+
let content;
|
|
1310
|
+
try {
|
|
1311
|
+
content = await readFile6(changelogPath, "utf-8");
|
|
1312
|
+
} catch {
|
|
1313
|
+
return [];
|
|
1314
|
+
}
|
|
1315
|
+
const sections = content.split(/^## \[/m);
|
|
1316
|
+
const relevant = [];
|
|
1317
|
+
for (const section of sections.slice(1)) {
|
|
1318
|
+
const match = section.match(/^([\d.]+)\]/);
|
|
1319
|
+
if (!match) continue;
|
|
1320
|
+
const ver = match[1];
|
|
1321
|
+
if (compareVersions(ver, fromVersion) !== "older" && compareVersions(ver, fromVersion) !== "equal" && (compareVersions(ver, toVersion) === "older" || compareVersions(ver, toVersion) === "equal")) {
|
|
1322
|
+
const lines = section.split("\n");
|
|
1323
|
+
for (const line of lines) {
|
|
1324
|
+
const stripped = line.trim();
|
|
1325
|
+
if (stripped.startsWith("- ")) {
|
|
1326
|
+
relevant.push(stripped);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
return relevant;
|
|
1332
|
+
}
|
|
1333
|
+
var init_version = __esm({
|
|
1334
|
+
"src/lib/version.ts"() {
|
|
1335
|
+
"use strict";
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
// src/lib/update-check.ts
|
|
1340
|
+
import { readFile as readFile7, writeFile as writeFile4, mkdir as mkdir4 } from "fs/promises";
|
|
1341
|
+
import { join as join10 } from "path";
|
|
1342
|
+
import { homedir } from "os";
|
|
1343
|
+
import pc5 from "picocolors";
|
|
1344
|
+
async function readCache() {
|
|
1345
|
+
try {
|
|
1346
|
+
const raw = await readFile7(CACHE_FILE, "utf-8");
|
|
1347
|
+
const parsed = JSON.parse(raw);
|
|
1348
|
+
if (typeof parsed === "object" && parsed !== null && "checkedAt" in parsed && "latestVersion" in parsed && typeof parsed.checkedAt === "number" && typeof parsed.latestVersion === "string") {
|
|
1349
|
+
return parsed;
|
|
1350
|
+
}
|
|
1351
|
+
return null;
|
|
1352
|
+
} catch {
|
|
1353
|
+
return null;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
async function writeCache(cache) {
|
|
1357
|
+
try {
|
|
1358
|
+
await mkdir4(join10(homedir(), ".flydocs"), { recursive: true });
|
|
1359
|
+
await writeFile4(CACHE_FILE, JSON.stringify(cache), "utf-8");
|
|
1360
|
+
} catch {
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
async function fetchLatestVersion() {
|
|
1364
|
+
try {
|
|
1365
|
+
const controller = new AbortController();
|
|
1366
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
1367
|
+
const response = await fetch(
|
|
1368
|
+
`https://registry.npmjs.org/${PACKAGE_NAME}/latest`,
|
|
1369
|
+
{ signal: controller.signal }
|
|
1370
|
+
);
|
|
1371
|
+
clearTimeout(timeout);
|
|
1372
|
+
if (!response.ok) {
|
|
1373
|
+
return null;
|
|
1374
|
+
}
|
|
1375
|
+
const data = await response.json();
|
|
1376
|
+
if (typeof data === "object" && data !== null && "version" in data && typeof data.version === "string") {
|
|
1377
|
+
return data.version;
|
|
1378
|
+
}
|
|
1379
|
+
return null;
|
|
1380
|
+
} catch {
|
|
1381
|
+
return null;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
async function checkForUpdate() {
|
|
1385
|
+
try {
|
|
1386
|
+
const cache = await readCache();
|
|
1387
|
+
if (cache && Date.now() - cache.checkedAt < CACHE_TTL_MS) {
|
|
1388
|
+
return {
|
|
1389
|
+
currentVersion: CLI_VERSION,
|
|
1390
|
+
latestVersion: cache.latestVersion,
|
|
1391
|
+
updateAvailable: compareVersions(CLI_VERSION, cache.latestVersion) === "older"
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
const latestVersion = await fetchLatestVersion();
|
|
1395
|
+
if (!latestVersion) {
|
|
1396
|
+
return null;
|
|
1397
|
+
}
|
|
1398
|
+
await writeCache({ checkedAt: Date.now(), latestVersion });
|
|
1399
|
+
return {
|
|
1400
|
+
currentVersion: CLI_VERSION,
|
|
1401
|
+
latestVersion,
|
|
1402
|
+
updateAvailable: compareVersions(CLI_VERSION, latestVersion) === "older"
|
|
1403
|
+
};
|
|
1404
|
+
} catch {
|
|
1405
|
+
return null;
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
function printUpdateNotice(result) {
|
|
1409
|
+
if (!result.updateAvailable) {
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
console.log();
|
|
1413
|
+
console.log(
|
|
1414
|
+
` ${pc5.dim("Update available:")} ${pc5.cyan(result.currentVersion)} ${pc5.dim("->")} ${pc5.cyan(result.latestVersion)}`
|
|
1415
|
+
);
|
|
1416
|
+
console.log(
|
|
1417
|
+
` ${pc5.dim("Run:")} ${pc5.yellow(`npm update -g ${PACKAGE_NAME}`)}`
|
|
1418
|
+
);
|
|
1419
|
+
console.log();
|
|
1420
|
+
}
|
|
1421
|
+
var CACHE_FILE, CACHE_TTL_MS, FETCH_TIMEOUT_MS;
|
|
1422
|
+
var init_update_check = __esm({
|
|
1423
|
+
"src/lib/update-check.ts"() {
|
|
1424
|
+
"use strict";
|
|
1425
|
+
init_constants();
|
|
1426
|
+
init_version();
|
|
1427
|
+
CACHE_FILE = join10(homedir(), ".flydocs", "update-check.json");
|
|
1428
|
+
CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1429
|
+
FETCH_TIMEOUT_MS = 5e3;
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
// src/commands/install.ts
|
|
1434
|
+
var install_exports = {};
|
|
1435
|
+
__export(install_exports, {
|
|
1436
|
+
default: () => install_default
|
|
1437
|
+
});
|
|
1438
|
+
import { defineCommand } from "citty";
|
|
1439
|
+
import { resolve as resolve2 } from "path";
|
|
1440
|
+
import { join as join11 } from "path";
|
|
1441
|
+
import { select, isCancel as isCancel3, cancel as cancel2 } from "@clack/prompts";
|
|
1442
|
+
var install_default;
|
|
1443
|
+
var init_install = __esm({
|
|
1444
|
+
"src/commands/install.ts"() {
|
|
1445
|
+
"use strict";
|
|
1446
|
+
init_template();
|
|
1447
|
+
init_fs_ops();
|
|
1448
|
+
init_ui();
|
|
1449
|
+
init_config();
|
|
1450
|
+
init_skills();
|
|
1451
|
+
init_stack();
|
|
1452
|
+
init_community_skills();
|
|
1453
|
+
init_deprecated();
|
|
1454
|
+
init_gitignore();
|
|
1455
|
+
init_post_install();
|
|
1456
|
+
init_update_check();
|
|
1457
|
+
install_default = defineCommand({
|
|
1458
|
+
meta: {
|
|
1459
|
+
name: "install",
|
|
1460
|
+
description: "Install FlyDocs in a project directory"
|
|
1461
|
+
},
|
|
1462
|
+
args: {
|
|
1463
|
+
tier: {
|
|
1464
|
+
type: "string",
|
|
1465
|
+
description: "Set tier: 'local' (free) or 'cloud' (Linear)"
|
|
1466
|
+
},
|
|
1467
|
+
path: {
|
|
1468
|
+
type: "string",
|
|
1469
|
+
description: "Install in the specified directory"
|
|
1470
|
+
},
|
|
1471
|
+
here: {
|
|
1472
|
+
type: "boolean",
|
|
1473
|
+
description: "Install in the current directory",
|
|
1474
|
+
default: false
|
|
1475
|
+
},
|
|
1476
|
+
"local-source": {
|
|
1477
|
+
type: "boolean",
|
|
1478
|
+
description: "Force use of local template/ directory",
|
|
1479
|
+
default: false
|
|
1480
|
+
},
|
|
1481
|
+
yes: {
|
|
1482
|
+
type: "boolean",
|
|
1483
|
+
alias: ["y"],
|
|
1484
|
+
description: "Accept all community skill recommendations",
|
|
1485
|
+
default: false
|
|
1486
|
+
}
|
|
1487
|
+
},
|
|
1488
|
+
async run({ args }) {
|
|
1489
|
+
const templateDir = await resolveTemplatePath(args["local-source"]);
|
|
1490
|
+
const version = await readTemplateVersion(templateDir);
|
|
1491
|
+
printBanner(version);
|
|
1492
|
+
let targetDir;
|
|
1493
|
+
if (args.path) {
|
|
1494
|
+
targetDir = resolve2(args.path.replace(/^~/, process.env.HOME ?? "~"));
|
|
1495
|
+
} else if (args.here) {
|
|
1496
|
+
targetDir = process.cwd();
|
|
1497
|
+
} else {
|
|
1498
|
+
targetDir = process.cwd();
|
|
1499
|
+
}
|
|
1500
|
+
if (!await pathExists(targetDir)) {
|
|
1501
|
+
printError(`Directory does not exist: ${targetDir}`);
|
|
1502
|
+
process.exit(1);
|
|
1503
|
+
}
|
|
1504
|
+
targetDir = resolve2(targetDir);
|
|
1505
|
+
if (targetDir !== process.cwd()) {
|
|
1506
|
+
printInfo(`Installing to: ${targetDir}`);
|
|
1507
|
+
console.log();
|
|
1508
|
+
}
|
|
1509
|
+
process.chdir(targetDir);
|
|
1510
|
+
let tier;
|
|
1511
|
+
if (args.tier) {
|
|
1512
|
+
if (args.tier !== "local" && args.tier !== "cloud") {
|
|
1513
|
+
printError(`Invalid tier: ${args.tier} (must be 'local' or 'cloud')`);
|
|
1514
|
+
process.exit(1);
|
|
1515
|
+
}
|
|
1516
|
+
tier = args.tier;
|
|
1517
|
+
printInfo(`Tier set via flag: ${tier}`);
|
|
1518
|
+
} else if (await pathExists(join11(targetDir, ".flydocs", "config.json"))) {
|
|
1519
|
+
try {
|
|
1520
|
+
const existing = await readConfig(targetDir);
|
|
1521
|
+
if (existing.tier) {
|
|
1522
|
+
tier = existing.tier;
|
|
1523
|
+
printInfo(`Tier from config: ${tier}`);
|
|
1524
|
+
}
|
|
1525
|
+
} catch {
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
if (!tier) {
|
|
1529
|
+
const tierChoice = await select({
|
|
1530
|
+
message: "Select your tier",
|
|
1531
|
+
options: [
|
|
1532
|
+
{
|
|
1533
|
+
value: "local",
|
|
1534
|
+
label: "local",
|
|
1535
|
+
hint: "File-based issues. No accounts, no API keys, works offline."
|
|
1536
|
+
},
|
|
1537
|
+
{
|
|
1538
|
+
value: "cloud",
|
|
1539
|
+
label: "cloud",
|
|
1540
|
+
hint: "Linear integration. Real-time sync with your team."
|
|
1541
|
+
}
|
|
1542
|
+
]
|
|
1543
|
+
});
|
|
1544
|
+
if (isCancel3(tierChoice)) {
|
|
1545
|
+
cancel2("Installation cancelled.");
|
|
1546
|
+
process.exit(0);
|
|
1547
|
+
}
|
|
1548
|
+
tier = tierChoice;
|
|
1549
|
+
console.log();
|
|
1550
|
+
}
|
|
1551
|
+
if (!await pathExists(join11(targetDir, ".git"))) {
|
|
1552
|
+
printError(
|
|
1553
|
+
"Not in a git repository. Please run from the root of your project."
|
|
1554
|
+
);
|
|
1555
|
+
process.exit(1);
|
|
1556
|
+
}
|
|
1557
|
+
await ensureDirectories(targetDir, tier);
|
|
1558
|
+
console.log("Installing framework files...");
|
|
1559
|
+
await replaceDirectory(
|
|
1560
|
+
join11(templateDir, ".flydocs", "templates"),
|
|
1561
|
+
join11(targetDir, ".flydocs", "templates")
|
|
1562
|
+
);
|
|
1563
|
+
await replaceDirectory(
|
|
1564
|
+
join11(templateDir, ".flydocs", "hooks"),
|
|
1565
|
+
join11(targetDir, ".flydocs", "hooks")
|
|
1566
|
+
);
|
|
1567
|
+
await replaceDirectory(
|
|
1568
|
+
join11(templateDir, ".flydocs", "scripts"),
|
|
1569
|
+
join11(targetDir, ".flydocs", "scripts")
|
|
1570
|
+
);
|
|
1571
|
+
await copyFile(
|
|
1572
|
+
join11(templateDir, ".flydocs", "version"),
|
|
1573
|
+
join11(targetDir, ".flydocs", "version")
|
|
1574
|
+
);
|
|
1575
|
+
const manifestSrc = join11(templateDir, "manifest.json");
|
|
1576
|
+
if (await pathExists(manifestSrc)) {
|
|
1577
|
+
await copyFile(manifestSrc, join11(targetDir, ".flydocs", "manifest.json"));
|
|
1578
|
+
}
|
|
1579
|
+
const changelogSrc = join11(templateDir, "CHANGELOG.md");
|
|
1580
|
+
if (await pathExists(changelogSrc)) {
|
|
1581
|
+
await copyFile(changelogSrc, join11(targetDir, ".flydocs", "CHANGELOG.md"));
|
|
1582
|
+
}
|
|
1583
|
+
printStatus(".flydocs/templates, hooks, version, manifest, changelog");
|
|
1584
|
+
const configPath = join11(targetDir, ".flydocs", "config.json");
|
|
1585
|
+
if (!await pathExists(configPath)) {
|
|
1586
|
+
const config = await createFreshConfig(templateDir, version, tier);
|
|
1587
|
+
await writeConfig(targetDir, config);
|
|
1588
|
+
printStatus(`.flydocs/config.json (new, tier: ${tier})`);
|
|
1589
|
+
} else if (args.tier) {
|
|
1590
|
+
try {
|
|
1591
|
+
const existing = await readConfig(targetDir);
|
|
1592
|
+
existing.version = version;
|
|
1593
|
+
existing.tier = tier;
|
|
1594
|
+
await writeConfig(targetDir, existing);
|
|
1595
|
+
printStatus(`.flydocs/config.json (tier updated: ${tier})`);
|
|
1596
|
+
} catch {
|
|
1597
|
+
const config = await createFreshConfig(templateDir, version, tier);
|
|
1598
|
+
await writeConfig(targetDir, config);
|
|
1599
|
+
printStatus(`.flydocs/config.json (recreated, tier: ${tier})`);
|
|
1600
|
+
}
|
|
1601
|
+
} else {
|
|
1602
|
+
printWarning(".flydocs/config.json exists, preserving");
|
|
1603
|
+
}
|
|
1604
|
+
console.log();
|
|
1605
|
+
console.log(`Installing skills (tier: ${tier})...`);
|
|
1606
|
+
await installOwnedSkills(templateDir, targetDir, tier);
|
|
1607
|
+
printStatus(`Skills installed (tier: ${tier})`);
|
|
1608
|
+
console.log();
|
|
1609
|
+
console.log("Installing agents and commands...");
|
|
1610
|
+
const claudeAgentsSrc = join11(templateDir, ".claude", "agents");
|
|
1611
|
+
if (await pathExists(claudeAgentsSrc)) {
|
|
1612
|
+
await copyDirectoryContents(
|
|
1613
|
+
claudeAgentsSrc,
|
|
1614
|
+
join11(targetDir, ".claude", "agents")
|
|
1615
|
+
);
|
|
1616
|
+
}
|
|
1617
|
+
await copyDirectoryContents(
|
|
1618
|
+
join11(templateDir, ".claude", "commands"),
|
|
1619
|
+
join11(targetDir, ".claude", "commands")
|
|
1620
|
+
);
|
|
1621
|
+
await copyFile(
|
|
1622
|
+
join11(templateDir, ".claude", "CLAUDE.md"),
|
|
1623
|
+
join11(targetDir, ".claude", "CLAUDE.md")
|
|
1624
|
+
);
|
|
1625
|
+
await copyFile(
|
|
1626
|
+
join11(templateDir, ".claude", "settings.json"),
|
|
1627
|
+
join11(targetDir, ".claude", "settings.json")
|
|
1628
|
+
);
|
|
1629
|
+
printStatus(".claude/ (agents, commands, CLAUDE.md, settings)");
|
|
1630
|
+
const cursorAgentsSrc = join11(templateDir, ".cursor", "agents");
|
|
1631
|
+
if (await pathExists(cursorAgentsSrc)) {
|
|
1632
|
+
await copyDirectoryContents(
|
|
1633
|
+
cursorAgentsSrc,
|
|
1634
|
+
join11(targetDir, ".cursor", "agents")
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
await copyDirectoryContents(
|
|
1638
|
+
join11(templateDir, ".claude", "commands"),
|
|
1639
|
+
join11(targetDir, ".cursor", "commands")
|
|
1640
|
+
);
|
|
1641
|
+
await copyFile(
|
|
1642
|
+
join11(templateDir, ".cursor", "hooks.json"),
|
|
1643
|
+
join11(targetDir, ".cursor", "hooks.json")
|
|
1644
|
+
);
|
|
1645
|
+
printStatus(".cursor/ (agents, commands, hooks)");
|
|
1646
|
+
await copyCursorRules(targetDir);
|
|
1647
|
+
printStatus(".cursor/rules/");
|
|
1648
|
+
const mcpResult = await copyFileIfNotExists(
|
|
1649
|
+
join11(templateDir, ".cursor", "mcp.json"),
|
|
1650
|
+
join11(targetDir, ".cursor", "mcp.json")
|
|
1651
|
+
);
|
|
1652
|
+
if (mcpResult === "created") {
|
|
1653
|
+
printStatus(".cursor/mcp.json (new)");
|
|
1654
|
+
} else {
|
|
1655
|
+
printWarning(".cursor/mcp.json exists, preserving");
|
|
1656
|
+
}
|
|
1657
|
+
await copyFile(
|
|
1658
|
+
join11(templateDir, "AGENTS.md"),
|
|
1659
|
+
join11(targetDir, "AGENTS.md")
|
|
1660
|
+
);
|
|
1661
|
+
printStatus("AGENTS.md");
|
|
1662
|
+
await runManifestGeneration(targetDir);
|
|
1663
|
+
await runContextGraphBuild(targetDir);
|
|
1664
|
+
console.log();
|
|
1665
|
+
console.log("Installing project templates...");
|
|
1666
|
+
const userFiles = [
|
|
1667
|
+
{
|
|
1668
|
+
src: join11(templateDir, "flydocs", "context", "project.md"),
|
|
1669
|
+
dest: join11(targetDir, "flydocs", "context", "project.md"),
|
|
1670
|
+
label: "flydocs/context/project.md"
|
|
1671
|
+
},
|
|
1672
|
+
{
|
|
1673
|
+
src: join11(templateDir, "flydocs", "knowledge", "INDEX.md"),
|
|
1674
|
+
dest: join11(targetDir, "flydocs", "knowledge", "INDEX.md"),
|
|
1675
|
+
label: "flydocs/knowledge/INDEX.md"
|
|
1676
|
+
},
|
|
1677
|
+
{
|
|
1678
|
+
src: join11(templateDir, "flydocs", "knowledge", "README.md"),
|
|
1679
|
+
dest: join11(targetDir, "flydocs", "knowledge", "README.md"),
|
|
1680
|
+
label: "flydocs/knowledge/README.md"
|
|
1681
|
+
},
|
|
1682
|
+
{
|
|
1683
|
+
src: join11(
|
|
1684
|
+
templateDir,
|
|
1685
|
+
"flydocs",
|
|
1686
|
+
"knowledge",
|
|
1687
|
+
"product",
|
|
1688
|
+
"personas.md"
|
|
1689
|
+
),
|
|
1690
|
+
dest: join11(targetDir, "flydocs", "knowledge", "product", "personas.md"),
|
|
1691
|
+
label: "flydocs/knowledge/product/personas.md"
|
|
1692
|
+
},
|
|
1693
|
+
{
|
|
1694
|
+
src: join11(
|
|
1695
|
+
templateDir,
|
|
1696
|
+
"flydocs",
|
|
1697
|
+
"knowledge",
|
|
1698
|
+
"product",
|
|
1699
|
+
"user-flows.md"
|
|
1700
|
+
),
|
|
1701
|
+
dest: join11(
|
|
1702
|
+
targetDir,
|
|
1703
|
+
"flydocs",
|
|
1704
|
+
"knowledge",
|
|
1705
|
+
"product",
|
|
1706
|
+
"user-flows.md"
|
|
1707
|
+
),
|
|
1708
|
+
label: "flydocs/knowledge/product/user-flows.md"
|
|
1709
|
+
},
|
|
1710
|
+
{
|
|
1711
|
+
src: join11(templateDir, "flydocs", "design-system", "README.md"),
|
|
1712
|
+
dest: join11(targetDir, "flydocs", "design-system", "README.md"),
|
|
1713
|
+
label: "flydocs/design-system/README.md"
|
|
1714
|
+
},
|
|
1715
|
+
{
|
|
1716
|
+
src: join11(
|
|
1717
|
+
templateDir,
|
|
1718
|
+
"flydocs",
|
|
1719
|
+
"design-system",
|
|
1720
|
+
"component-patterns.md"
|
|
1721
|
+
),
|
|
1722
|
+
dest: join11(
|
|
1723
|
+
targetDir,
|
|
1724
|
+
"flydocs",
|
|
1725
|
+
"design-system",
|
|
1726
|
+
"component-patterns.md"
|
|
1727
|
+
),
|
|
1728
|
+
label: "flydocs/design-system/component-patterns.md"
|
|
1729
|
+
},
|
|
1730
|
+
{
|
|
1731
|
+
src: join11(templateDir, "flydocs", "design-system", "token-mapping.md"),
|
|
1732
|
+
dest: join11(targetDir, "flydocs", "design-system", "token-mapping.md"),
|
|
1733
|
+
label: "flydocs/design-system/token-mapping.md"
|
|
1734
|
+
},
|
|
1735
|
+
{
|
|
1736
|
+
src: join11(templateDir, "flydocs", "README.md"),
|
|
1737
|
+
dest: join11(targetDir, "flydocs", "README.md"),
|
|
1738
|
+
label: "flydocs/README.md"
|
|
1739
|
+
}
|
|
1740
|
+
];
|
|
1741
|
+
for (const file of userFiles) {
|
|
1742
|
+
if (await pathExists(file.src)) {
|
|
1743
|
+
const result = await copyFileIfNotExists(file.src, file.dest);
|
|
1744
|
+
if (result === "created") {
|
|
1745
|
+
printStatus(`${file.label} (new)`);
|
|
1746
|
+
} else {
|
|
1747
|
+
printWarning(`${file.label} exists, preserving`);
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
const envExampleSrc = join11(templateDir, ".env.example");
|
|
1752
|
+
if (await pathExists(envExampleSrc)) {
|
|
1753
|
+
const hasEnv = await pathExists(join11(targetDir, ".env"));
|
|
1754
|
+
const hasEnvExample = await pathExists(join11(targetDir, ".env.example"));
|
|
1755
|
+
if (!hasEnv && !hasEnvExample) {
|
|
1756
|
+
await copyFile(envExampleSrc, join11(targetDir, ".env.example"));
|
|
1757
|
+
printStatus(".env.example (new)");
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
await ensureGitignore(targetDir);
|
|
1761
|
+
console.log();
|
|
1762
|
+
console.log("Detecting project stack...");
|
|
1763
|
+
const stack = await detectStack(targetDir);
|
|
1764
|
+
if (stack.raw.length > 0) {
|
|
1765
|
+
printStatus(`Detected: ${stack.raw.join(", ")}`);
|
|
1766
|
+
try {
|
|
1767
|
+
const config = await readConfig(targetDir);
|
|
1768
|
+
config.detectedStack = stackToDetectedConfig(stack);
|
|
1769
|
+
await writeConfig(targetDir, config);
|
|
1770
|
+
} catch {
|
|
1771
|
+
}
|
|
1772
|
+
await promptCommunitySkills(targetDir, stack, args.yes);
|
|
1773
|
+
} else {
|
|
1774
|
+
printInfo("No framework detected in package.json");
|
|
1775
|
+
}
|
|
1776
|
+
console.log();
|
|
1777
|
+
console.log("Checking for deprecated files...");
|
|
1778
|
+
const deprecated = await scanDeprecated(targetDir);
|
|
1779
|
+
await promptCleanup(targetDir, deprecated);
|
|
1780
|
+
const nextSteps = tier === "local" ? [
|
|
1781
|
+
`Tier: ${tier}`,
|
|
1782
|
+
`Version: ${version}`,
|
|
1783
|
+
"",
|
|
1784
|
+
"Next steps:",
|
|
1785
|
+
" 1. Run /flydocs-setup to configure your project",
|
|
1786
|
+
" 2. Start working with /start-session",
|
|
1787
|
+
"",
|
|
1788
|
+
"Documentation: flydocs/README.md"
|
|
1789
|
+
] : [
|
|
1790
|
+
`Tier: ${tier}`,
|
|
1791
|
+
`Version: ${version}`,
|
|
1792
|
+
"",
|
|
1793
|
+
"Next steps:",
|
|
1794
|
+
" 1. Copy your LINEAR_API_KEY to .env",
|
|
1795
|
+
" 2. Run /flydocs-setup to configure your Linear workspace",
|
|
1796
|
+
" 3. Start working with /start-session",
|
|
1797
|
+
"",
|
|
1798
|
+
"Documentation: flydocs/README.md"
|
|
1799
|
+
];
|
|
1800
|
+
printCompletionBox("Installation Complete!", nextSteps);
|
|
1801
|
+
printBetaCta();
|
|
1802
|
+
try {
|
|
1803
|
+
const updateResult = await checkForUpdate();
|
|
1804
|
+
if (updateResult) {
|
|
1805
|
+
printUpdateNotice(updateResult);
|
|
1806
|
+
}
|
|
1807
|
+
} catch {
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
});
|
|
1813
|
+
|
|
1814
|
+
// src/commands/update.ts
|
|
1815
|
+
var update_exports = {};
|
|
1816
|
+
__export(update_exports, {
|
|
1817
|
+
default: () => update_default
|
|
1818
|
+
});
|
|
1819
|
+
import { defineCommand as defineCommand2 } from "citty";
|
|
1820
|
+
import { resolve as resolve3, join as join12 } from "path";
|
|
1821
|
+
import { mkdir as mkdir5, cp as cp2, readFile as readFile8, readdir as readdir3, rm as rm4 } from "fs/promises";
|
|
1822
|
+
import { select as select2, text, confirm as confirm2, isCancel as isCancel4, cancel as cancel3 } from "@clack/prompts";
|
|
1823
|
+
import pc6 from "picocolors";
|
|
1824
|
+
var update_default;
|
|
1825
|
+
var init_update = __esm({
|
|
1826
|
+
"src/commands/update.ts"() {
|
|
1827
|
+
"use strict";
|
|
1828
|
+
init_template();
|
|
1829
|
+
init_version();
|
|
1830
|
+
init_fs_ops();
|
|
1831
|
+
init_ui();
|
|
1832
|
+
init_config();
|
|
1833
|
+
init_skills();
|
|
1834
|
+
init_stack();
|
|
1835
|
+
init_community_skills();
|
|
1836
|
+
init_deprecated();
|
|
1837
|
+
init_gitignore();
|
|
1838
|
+
init_post_install();
|
|
1839
|
+
init_update_check();
|
|
1840
|
+
update_default = defineCommand2({
|
|
1841
|
+
meta: {
|
|
1842
|
+
name: "update",
|
|
1843
|
+
description: "Update an existing FlyDocs installation"
|
|
1844
|
+
},
|
|
1845
|
+
args: {
|
|
1846
|
+
path: {
|
|
1847
|
+
type: "string",
|
|
1848
|
+
description: "Update the specified project directory"
|
|
1849
|
+
},
|
|
1850
|
+
here: {
|
|
1851
|
+
type: "boolean",
|
|
1852
|
+
description: "Update in the current directory",
|
|
1853
|
+
default: false
|
|
1854
|
+
},
|
|
1855
|
+
tier: {
|
|
1856
|
+
type: "string",
|
|
1857
|
+
description: "Change tier: 'local' (free) or 'cloud' (Linear)"
|
|
1858
|
+
},
|
|
1859
|
+
force: {
|
|
1860
|
+
type: "boolean",
|
|
1861
|
+
description: "Force update even if versions match",
|
|
1862
|
+
default: false
|
|
1863
|
+
},
|
|
1864
|
+
"local-source": {
|
|
1865
|
+
type: "boolean",
|
|
1866
|
+
description: "Force use of local template/ directory",
|
|
1867
|
+
default: false
|
|
1868
|
+
},
|
|
1869
|
+
yes: {
|
|
1870
|
+
type: "boolean",
|
|
1871
|
+
alias: ["y"],
|
|
1872
|
+
description: "Accept all community skill recommendations",
|
|
1873
|
+
default: false
|
|
1874
|
+
}
|
|
1875
|
+
},
|
|
1876
|
+
async run({ args }) {
|
|
1877
|
+
const templateDir = await resolveTemplatePath(args["local-source"]);
|
|
1878
|
+
const version = await readTemplateVersion(templateDir);
|
|
1879
|
+
printBanner(version);
|
|
1880
|
+
printInfo("Running in update mode \u2014 framework files will be refreshed");
|
|
1881
|
+
console.log();
|
|
1882
|
+
let targetDir;
|
|
1883
|
+
if (args.path) {
|
|
1884
|
+
targetDir = resolve3(args.path.replace(/^~/, process.env.HOME ?? "~"));
|
|
1885
|
+
} else if (args.here) {
|
|
1886
|
+
targetDir = process.cwd();
|
|
1887
|
+
} else {
|
|
1888
|
+
const choice = await select2({
|
|
1889
|
+
message: "Which project would you like to update?",
|
|
1890
|
+
options: [
|
|
1891
|
+
{
|
|
1892
|
+
value: "cwd",
|
|
1893
|
+
label: `Current directory (${process.cwd()})`
|
|
1894
|
+
},
|
|
1895
|
+
{
|
|
1896
|
+
value: "enter",
|
|
1897
|
+
label: "Enter a path"
|
|
1898
|
+
}
|
|
1899
|
+
]
|
|
1900
|
+
});
|
|
1901
|
+
if (isCancel4(choice)) {
|
|
1902
|
+
cancel3("Update cancelled.");
|
|
1903
|
+
process.exit(0);
|
|
1904
|
+
}
|
|
1905
|
+
if (choice === "cwd") {
|
|
1906
|
+
targetDir = process.cwd();
|
|
1907
|
+
} else {
|
|
1908
|
+
const enteredPath = await text({
|
|
1909
|
+
message: "Enter project path:"
|
|
1910
|
+
});
|
|
1911
|
+
if (isCancel4(enteredPath)) {
|
|
1912
|
+
cancel3("Update cancelled.");
|
|
1913
|
+
process.exit(0);
|
|
1914
|
+
}
|
|
1915
|
+
targetDir = resolve3(
|
|
1916
|
+
enteredPath.replace(/^~/, process.env.HOME ?? "~")
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
if (!await pathExists(targetDir)) {
|
|
1921
|
+
printError(`Directory does not exist: ${targetDir}`);
|
|
1922
|
+
process.exit(1);
|
|
1923
|
+
}
|
|
1924
|
+
targetDir = resolve3(targetDir);
|
|
1925
|
+
process.chdir(targetDir);
|
|
1926
|
+
const hasVersion = await pathExists(join12(targetDir, ".flydocs", "version"));
|
|
1927
|
+
const hasConfig = await pathExists(
|
|
1928
|
+
join12(targetDir, ".flydocs", "config.json")
|
|
1929
|
+
);
|
|
1930
|
+
if (!hasVersion && !hasConfig) {
|
|
1931
|
+
printError(`Not a FlyDocs project: ${targetDir}`);
|
|
1932
|
+
printInfo("Missing .flydocs/version or .flydocs/config.json");
|
|
1933
|
+
process.exit(1);
|
|
1934
|
+
}
|
|
1935
|
+
printInfo(`Updating project at: ${targetDir}`);
|
|
1936
|
+
console.log();
|
|
1937
|
+
let currentVersion = "0.1.0";
|
|
1938
|
+
if (hasVersion) {
|
|
1939
|
+
const vContent = await readFile8(
|
|
1940
|
+
join12(targetDir, ".flydocs", "version"),
|
|
1941
|
+
"utf-8"
|
|
1942
|
+
);
|
|
1943
|
+
currentVersion = vContent.trim();
|
|
1944
|
+
}
|
|
1945
|
+
const versionStatus = compareVersions(currentVersion, version);
|
|
1946
|
+
if (versionStatus === "equal" && !args.force) {
|
|
1947
|
+
printStatus(`Already up to date (v${currentVersion})`);
|
|
1948
|
+
console.log();
|
|
1949
|
+
console.log(
|
|
1950
|
+
"No changes needed. Use --force to re-apply framework files."
|
|
1951
|
+
);
|
|
1952
|
+
process.exit(0);
|
|
1953
|
+
} else if (versionStatus === "equal" && args.force) {
|
|
1954
|
+
printInfo(`Forcing update at v${currentVersion}`);
|
|
1955
|
+
} else if (versionStatus === "newer") {
|
|
1956
|
+
printWarning(
|
|
1957
|
+
`Project version (${currentVersion}) is newer than installer (${version})`
|
|
1958
|
+
);
|
|
1959
|
+
const shouldContinue = await confirm2({
|
|
1960
|
+
message: "Continue anyway?"
|
|
1961
|
+
});
|
|
1962
|
+
if (isCancel4(shouldContinue) || !shouldContinue) {
|
|
1963
|
+
process.exit(0);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
console.log(`Updating: v${currentVersion} \u2192 v${version}`);
|
|
1967
|
+
console.log();
|
|
1968
|
+
const changelogPath = join12(templateDir, "CHANGELOG.md");
|
|
1969
|
+
const whatsNew = await getWhatsNew(changelogPath, currentVersion, version);
|
|
1970
|
+
if (whatsNew.length > 0) {
|
|
1971
|
+
console.log(pc6.cyan("What's new:"));
|
|
1972
|
+
console.log();
|
|
1973
|
+
for (const entry of whatsNew) {
|
|
1974
|
+
console.log(` ${entry}`);
|
|
1975
|
+
}
|
|
1976
|
+
console.log();
|
|
1977
|
+
}
|
|
1978
|
+
const now = /* @__PURE__ */ new Date();
|
|
1979
|
+
const ts = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}-${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}${String(now.getSeconds()).padStart(2, "0")}`;
|
|
1980
|
+
const backupDir = join12(targetDir, ".flydocs", `backup-${ts}`);
|
|
1981
|
+
await mkdir5(backupDir, { recursive: true });
|
|
1982
|
+
if (hasConfig) {
|
|
1983
|
+
await cp2(
|
|
1984
|
+
join12(targetDir, ".flydocs", "config.json"),
|
|
1985
|
+
join12(backupDir, "config.json")
|
|
1986
|
+
);
|
|
1987
|
+
printStatus(`Config backed up to .flydocs/backup-${ts}/`);
|
|
1988
|
+
}
|
|
1989
|
+
try {
|
|
1990
|
+
const flydocsDir = join12(targetDir, ".flydocs");
|
|
1991
|
+
const entries = await readdir3(flydocsDir);
|
|
1992
|
+
const backups = entries.filter((e) => e.startsWith("backup-")).sort();
|
|
1993
|
+
if (backups.length > 3) {
|
|
1994
|
+
const toRemove = backups.slice(0, backups.length - 3);
|
|
1995
|
+
for (const old of toRemove) {
|
|
1996
|
+
await rm4(join12(flydocsDir, old), { recursive: true, force: true });
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
} catch {
|
|
2000
|
+
}
|
|
2001
|
+
let preserved = {
|
|
2002
|
+
tier: "cloud",
|
|
2003
|
+
setupComplete: false,
|
|
2004
|
+
providerTeamId: null,
|
|
2005
|
+
workspace: {},
|
|
2006
|
+
issueLabels: {},
|
|
2007
|
+
statusMapping: {},
|
|
2008
|
+
detectedStack: {},
|
|
2009
|
+
mcp: {},
|
|
2010
|
+
skills: {},
|
|
2011
|
+
designSystem: null,
|
|
2012
|
+
aiLabor: {}
|
|
2013
|
+
};
|
|
2014
|
+
if (hasConfig) {
|
|
2015
|
+
try {
|
|
2016
|
+
const existingConfig = await readConfig(targetDir);
|
|
2017
|
+
preserved = extractPreservedValues(existingConfig);
|
|
2018
|
+
} catch {
|
|
2019
|
+
printWarning("Could not read existing config \u2014 using defaults");
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
let effectiveTier;
|
|
2023
|
+
if (args.tier) {
|
|
2024
|
+
if (args.tier !== "local" && args.tier !== "cloud") {
|
|
2025
|
+
printError(`Invalid tier: ${args.tier} (must be 'local' or 'cloud')`);
|
|
2026
|
+
process.exit(1);
|
|
2027
|
+
}
|
|
2028
|
+
effectiveTier = args.tier;
|
|
2029
|
+
} else {
|
|
2030
|
+
effectiveTier = preserved.tier;
|
|
2031
|
+
}
|
|
2032
|
+
console.log("Replacing framework directories...");
|
|
2033
|
+
await replaceDirectory(
|
|
2034
|
+
join12(templateDir, ".flydocs", "templates"),
|
|
2035
|
+
join12(targetDir, ".flydocs", "templates")
|
|
2036
|
+
);
|
|
2037
|
+
await replaceDirectory(
|
|
2038
|
+
join12(templateDir, ".flydocs", "hooks"),
|
|
2039
|
+
join12(targetDir, ".flydocs", "hooks")
|
|
2040
|
+
);
|
|
2041
|
+
await replaceDirectory(
|
|
2042
|
+
join12(templateDir, ".flydocs", "scripts"),
|
|
2043
|
+
join12(targetDir, ".flydocs", "scripts")
|
|
2044
|
+
);
|
|
2045
|
+
printStatus(".flydocs/templates, hooks, scripts");
|
|
2046
|
+
const claudeAgentsSrc = join12(templateDir, ".claude", "agents");
|
|
2047
|
+
if (await pathExists(claudeAgentsSrc)) {
|
|
2048
|
+
await copyDirectoryContents(
|
|
2049
|
+
claudeAgentsSrc,
|
|
2050
|
+
join12(targetDir, ".claude", "agents")
|
|
2051
|
+
);
|
|
2052
|
+
}
|
|
2053
|
+
printStatus(".claude/agents");
|
|
2054
|
+
await replaceOwnedSkills(templateDir, targetDir, effectiveTier);
|
|
2055
|
+
printStatus(`.claude/skills (tier: ${effectiveTier})`);
|
|
2056
|
+
const cursorAgentsSrc = join12(templateDir, ".cursor", "agents");
|
|
2057
|
+
if (await pathExists(cursorAgentsSrc)) {
|
|
2058
|
+
await copyDirectoryContents(
|
|
2059
|
+
cursorAgentsSrc,
|
|
2060
|
+
join12(targetDir, ".cursor", "agents")
|
|
2061
|
+
);
|
|
2062
|
+
}
|
|
2063
|
+
printStatus(".cursor/agents");
|
|
2064
|
+
console.log();
|
|
2065
|
+
console.log("Replacing framework files...");
|
|
2066
|
+
await copyFile(
|
|
2067
|
+
join12(templateDir, ".claude", "CLAUDE.md"),
|
|
2068
|
+
join12(targetDir, ".claude", "CLAUDE.md")
|
|
2069
|
+
);
|
|
2070
|
+
await copyFile(
|
|
2071
|
+
join12(templateDir, ".claude", "settings.json"),
|
|
2072
|
+
join12(targetDir, ".claude", "settings.json")
|
|
2073
|
+
);
|
|
2074
|
+
printStatus(".claude/CLAUDE.md, settings.json");
|
|
2075
|
+
await copyDirectoryContents(
|
|
2076
|
+
join12(templateDir, ".claude", "commands"),
|
|
2077
|
+
join12(targetDir, ".claude", "commands")
|
|
2078
|
+
);
|
|
2079
|
+
await copyDirectoryContents(
|
|
2080
|
+
join12(templateDir, ".claude", "commands"),
|
|
2081
|
+
join12(targetDir, ".cursor", "commands")
|
|
2082
|
+
);
|
|
2083
|
+
printStatus(".claude/commands, .cursor/commands");
|
|
2084
|
+
const skillsReadmeSrc = join12(templateDir, ".claude", "skills", "README.md");
|
|
2085
|
+
if (await pathExists(skillsReadmeSrc)) {
|
|
2086
|
+
await copyFile(
|
|
2087
|
+
skillsReadmeSrc,
|
|
2088
|
+
join12(targetDir, ".claude", "skills", "README.md")
|
|
2089
|
+
);
|
|
2090
|
+
}
|
|
2091
|
+
printStatus(".claude/skills/README.md");
|
|
2092
|
+
await copyFile(
|
|
2093
|
+
join12(templateDir, ".cursor", "hooks.json"),
|
|
2094
|
+
join12(targetDir, ".cursor", "hooks.json")
|
|
2095
|
+
);
|
|
2096
|
+
printStatus(".cursor/hooks.json");
|
|
2097
|
+
await copyFile(
|
|
2098
|
+
join12(templateDir, "AGENTS.md"),
|
|
2099
|
+
join12(targetDir, "AGENTS.md")
|
|
2100
|
+
);
|
|
2101
|
+
printStatus("AGENTS.md");
|
|
2102
|
+
await runManifestGeneration(targetDir);
|
|
2103
|
+
await runContextGraphBuild(targetDir);
|
|
2104
|
+
console.log();
|
|
2105
|
+
console.log("Updating cursor rules...");
|
|
2106
|
+
await copyCursorRules(targetDir);
|
|
2107
|
+
printStatus(".cursor/rules/");
|
|
2108
|
+
console.log();
|
|
2109
|
+
console.log("Merging configuration...");
|
|
2110
|
+
try {
|
|
2111
|
+
const tierFlag = args.tier;
|
|
2112
|
+
const mergedConfig = await mergeConfig(
|
|
2113
|
+
templateDir,
|
|
2114
|
+
version,
|
|
2115
|
+
tierFlag,
|
|
2116
|
+
preserved
|
|
2117
|
+
);
|
|
2118
|
+
await writeConfig(targetDir, mergedConfig);
|
|
2119
|
+
printStatus(".flydocs/config.json (merged)");
|
|
2120
|
+
} catch {
|
|
2121
|
+
printWarning("Config merge failed \u2014 config.json preserved as-is");
|
|
2122
|
+
}
|
|
2123
|
+
await copyFile(
|
|
2124
|
+
join12(templateDir, ".flydocs", "version"),
|
|
2125
|
+
join12(targetDir, ".flydocs", "version")
|
|
2126
|
+
);
|
|
2127
|
+
printStatus(`.flydocs/version \u2192 ${version}`);
|
|
2128
|
+
const clSrc = join12(templateDir, "CHANGELOG.md");
|
|
2129
|
+
if (await pathExists(clSrc)) {
|
|
2130
|
+
await copyFile(clSrc, join12(targetDir, ".flydocs", "CHANGELOG.md"));
|
|
2131
|
+
printStatus(".flydocs/CHANGELOG.md");
|
|
2132
|
+
}
|
|
2133
|
+
const mfSrc = join12(templateDir, "manifest.json");
|
|
2134
|
+
if (await pathExists(mfSrc)) {
|
|
2135
|
+
await copyFile(mfSrc, join12(targetDir, ".flydocs", "manifest.json"));
|
|
2136
|
+
printStatus(".flydocs/manifest.json");
|
|
2137
|
+
}
|
|
2138
|
+
console.log();
|
|
2139
|
+
console.log("Detecting project stack...");
|
|
2140
|
+
const stack = await detectStack(targetDir);
|
|
2141
|
+
if (stack.raw.length > 0) {
|
|
2142
|
+
printStatus(`Detected: ${stack.raw.join(", ")}`);
|
|
2143
|
+
try {
|
|
2144
|
+
const config = await readConfig(targetDir);
|
|
2145
|
+
config.detectedStack = stackToDetectedConfig(stack);
|
|
2146
|
+
await writeConfig(targetDir, config);
|
|
2147
|
+
} catch {
|
|
2148
|
+
}
|
|
2149
|
+
await promptCommunitySkills(targetDir, stack, args.yes);
|
|
2150
|
+
} else {
|
|
2151
|
+
printInfo("No framework detected in package.json");
|
|
2152
|
+
}
|
|
2153
|
+
await migrateGitignore(targetDir);
|
|
2154
|
+
console.log();
|
|
2155
|
+
console.log("Checking for deprecated files...");
|
|
2156
|
+
await handleLegacyContext(targetDir);
|
|
2157
|
+
const legacyPath = await checkLegacyFolder(targetDir);
|
|
2158
|
+
const deprecated = await scanDeprecated(targetDir);
|
|
2159
|
+
if (legacyPath) {
|
|
2160
|
+
deprecated.push(legacyPath);
|
|
2161
|
+
}
|
|
2162
|
+
await promptCleanup(targetDir, deprecated);
|
|
2163
|
+
printCompletionBox("Update Complete!", [
|
|
2164
|
+
`v${currentVersion} \u2192 v${version}`,
|
|
2165
|
+
`Tier: ${effectiveTier}`,
|
|
2166
|
+
"Config: preserved and merged",
|
|
2167
|
+
"",
|
|
2168
|
+
`Backup: .flydocs/backup-${ts}/`
|
|
2169
|
+
]);
|
|
2170
|
+
printBetaCta();
|
|
2171
|
+
try {
|
|
2172
|
+
const updateResult = await checkForUpdate();
|
|
2173
|
+
if (updateResult) {
|
|
2174
|
+
printUpdateNotice(updateResult);
|
|
2175
|
+
}
|
|
2176
|
+
} catch {
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
});
|
|
2180
|
+
}
|
|
2181
|
+
});
|
|
2182
|
+
|
|
2183
|
+
// src/commands/setup.ts
|
|
2184
|
+
var setup_exports = {};
|
|
2185
|
+
__export(setup_exports, {
|
|
2186
|
+
default: () => setup_default
|
|
2187
|
+
});
|
|
2188
|
+
import { defineCommand as defineCommand3 } from "citty";
|
|
2189
|
+
var setup_default;
|
|
2190
|
+
var init_setup = __esm({
|
|
2191
|
+
"src/commands/setup.ts"() {
|
|
2192
|
+
"use strict";
|
|
2193
|
+
init_ui();
|
|
2194
|
+
setup_default = defineCommand3({
|
|
2195
|
+
meta: {
|
|
2196
|
+
name: "setup",
|
|
2197
|
+
description: "Configure FlyDocs settings for this project"
|
|
2198
|
+
},
|
|
2199
|
+
run() {
|
|
2200
|
+
printStub("flydocs setup");
|
|
2201
|
+
}
|
|
2202
|
+
});
|
|
2203
|
+
}
|
|
2204
|
+
});
|
|
2205
|
+
|
|
2206
|
+
// src/commands/skills.ts
|
|
2207
|
+
var skills_exports = {};
|
|
2208
|
+
__export(skills_exports, {
|
|
2209
|
+
default: () => skills_default
|
|
2210
|
+
});
|
|
2211
|
+
import { defineCommand as defineCommand4 } from "citty";
|
|
2212
|
+
import pc7 from "picocolors";
|
|
2213
|
+
var list, search, add, remove, skills_default;
|
|
2214
|
+
var init_skills2 = __esm({
|
|
2215
|
+
"src/commands/skills.ts"() {
|
|
2216
|
+
"use strict";
|
|
2217
|
+
init_skill_manager();
|
|
2218
|
+
list = defineCommand4({
|
|
2219
|
+
meta: {
|
|
2220
|
+
name: "list",
|
|
2221
|
+
description: "List installed skills"
|
|
2222
|
+
},
|
|
2223
|
+
async run() {
|
|
2224
|
+
const result = await listSkills(process.cwd());
|
|
2225
|
+
const total = result.platform.length + result.community.length;
|
|
2226
|
+
if (total === 0) {
|
|
2227
|
+
console.log("No skills installed.");
|
|
2228
|
+
return;
|
|
2229
|
+
}
|
|
2230
|
+
console.log();
|
|
2231
|
+
console.log(`${total} skill(s) installed:`);
|
|
2232
|
+
if (result.platform.length > 0) {
|
|
2233
|
+
console.log();
|
|
2234
|
+
console.log(pc7.bold("Platform"));
|
|
2235
|
+
for (const skill of result.platform) {
|
|
2236
|
+
console.log(
|
|
2237
|
+
` ${skill.name} ${pc7.dim(`(${skill.triggers} triggers)`)}`
|
|
2238
|
+
);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
if (result.community.length > 0) {
|
|
2242
|
+
console.log();
|
|
2243
|
+
console.log(pc7.bold("Community"));
|
|
2244
|
+
for (const skill of result.community) {
|
|
2245
|
+
console.log(
|
|
2246
|
+
` ${skill.name} ${pc7.dim(`(${skill.triggers} triggers)`)}`
|
|
2247
|
+
);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
console.log();
|
|
2251
|
+
}
|
|
2252
|
+
});
|
|
2253
|
+
search = defineCommand4({
|
|
2254
|
+
meta: {
|
|
2255
|
+
name: "search",
|
|
2256
|
+
description: "Search community skills"
|
|
2257
|
+
},
|
|
2258
|
+
args: {
|
|
2259
|
+
keyword: {
|
|
2260
|
+
type: "positional",
|
|
2261
|
+
description: "Keyword to search for",
|
|
2262
|
+
required: true
|
|
2263
|
+
}
|
|
2264
|
+
},
|
|
2265
|
+
async run({ args }) {
|
|
2266
|
+
const results = await searchCatalog(args.keyword);
|
|
2267
|
+
if (results.length === 0) {
|
|
2268
|
+
console.log(`No skills found for "${args.keyword}".`);
|
|
2269
|
+
console.log(` Browse the catalog at: ${pc7.cyan("https://skills.sh/")}`);
|
|
2270
|
+
return;
|
|
2271
|
+
}
|
|
2272
|
+
console.log();
|
|
2273
|
+
console.log(`${results.length} skill(s) matching "${args.keyword}":`);
|
|
2274
|
+
console.log();
|
|
2275
|
+
for (const skill of results) {
|
|
2276
|
+
console.log(` ${pc7.bold(skill.name)}`);
|
|
2277
|
+
console.log(` ${skill.description}`);
|
|
2278
|
+
console.log(` ${pc7.dim(skill.repo)}`);
|
|
2279
|
+
if (skill.tags.length > 0) {
|
|
2280
|
+
console.log(` ${pc7.dim(skill.tags.join(", "))}`);
|
|
2281
|
+
}
|
|
2282
|
+
console.log();
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2285
|
+
});
|
|
2286
|
+
add = defineCommand4({
|
|
2287
|
+
meta: {
|
|
2288
|
+
name: "add",
|
|
2289
|
+
description: "Install a community skill"
|
|
2290
|
+
},
|
|
2291
|
+
args: {
|
|
2292
|
+
source: {
|
|
2293
|
+
type: "positional",
|
|
2294
|
+
description: "Skill source (name or URL)",
|
|
2295
|
+
required: true
|
|
2296
|
+
}
|
|
2297
|
+
},
|
|
2298
|
+
async run({ args }) {
|
|
2299
|
+
await addSkill(process.cwd(), args.source);
|
|
2300
|
+
}
|
|
2301
|
+
});
|
|
2302
|
+
remove = defineCommand4({
|
|
2303
|
+
meta: {
|
|
2304
|
+
name: "remove",
|
|
2305
|
+
description: "Remove an installed community skill"
|
|
2306
|
+
},
|
|
2307
|
+
args: {
|
|
2308
|
+
name: {
|
|
2309
|
+
type: "positional",
|
|
2310
|
+
description: "Skill name to remove",
|
|
2311
|
+
required: true
|
|
2312
|
+
}
|
|
2313
|
+
},
|
|
2314
|
+
async run({ args }) {
|
|
2315
|
+
await removeSkill(process.cwd(), args.name);
|
|
2316
|
+
}
|
|
2317
|
+
});
|
|
2318
|
+
skills_default = defineCommand4({
|
|
2319
|
+
meta: {
|
|
2320
|
+
name: "skills",
|
|
2321
|
+
description: "Manage FlyDocs skills (list, search, add, remove)"
|
|
2322
|
+
},
|
|
2323
|
+
subCommands: {
|
|
2324
|
+
list,
|
|
2325
|
+
search,
|
|
2326
|
+
add,
|
|
2327
|
+
remove
|
|
2328
|
+
}
|
|
2329
|
+
});
|
|
2330
|
+
}
|
|
2331
|
+
});
|
|
2332
|
+
|
|
2333
|
+
// src/commands/connect.ts
|
|
2334
|
+
var connect_exports = {};
|
|
2335
|
+
__export(connect_exports, {
|
|
2336
|
+
default: () => connect_default
|
|
2337
|
+
});
|
|
2338
|
+
import { defineCommand as defineCommand5 } from "citty";
|
|
2339
|
+
import { text as text2, confirm as confirm3, isCancel as isCancel5, cancel as cancel4 } from "@clack/prompts";
|
|
2340
|
+
import pc8 from "picocolors";
|
|
2341
|
+
import { readFile as readFile9, writeFile as writeFile5, appendFile as appendFile2 } from "fs/promises";
|
|
2342
|
+
import { join as join13 } from "path";
|
|
2343
|
+
import { execSync as execSync2 } from "child_process";
|
|
2344
|
+
var connect_default;
|
|
2345
|
+
var init_connect = __esm({
|
|
2346
|
+
"src/commands/connect.ts"() {
|
|
2347
|
+
"use strict";
|
|
2348
|
+
init_config();
|
|
2349
|
+
init_fs_ops();
|
|
2350
|
+
init_template();
|
|
2351
|
+
init_ui();
|
|
2352
|
+
connect_default = defineCommand5({
|
|
2353
|
+
meta: {
|
|
2354
|
+
name: "connect",
|
|
2355
|
+
description: "Connect FlyDocs to a cloud provider (Linear)"
|
|
2356
|
+
},
|
|
2357
|
+
args: {
|
|
2358
|
+
path: {
|
|
2359
|
+
type: "string",
|
|
2360
|
+
description: "Path to FlyDocs project"
|
|
2361
|
+
},
|
|
2362
|
+
here: {
|
|
2363
|
+
type: "boolean",
|
|
2364
|
+
description: "Use current directory"
|
|
2365
|
+
},
|
|
2366
|
+
"local-source": {
|
|
2367
|
+
type: "boolean",
|
|
2368
|
+
description: "Use local template directory"
|
|
2369
|
+
},
|
|
2370
|
+
key: {
|
|
2371
|
+
type: "string",
|
|
2372
|
+
description: "Linear API key (skips prompt)"
|
|
2373
|
+
}
|
|
2374
|
+
},
|
|
2375
|
+
async run({ args }) {
|
|
2376
|
+
const targetDir = args.path ?? process.cwd();
|
|
2377
|
+
const configPath = join13(targetDir, ".flydocs", "config.json");
|
|
2378
|
+
if (!await pathExists(configPath)) {
|
|
2379
|
+
printError("Not a FlyDocs project (.flydocs/config.json not found).");
|
|
2380
|
+
console.log(
|
|
2381
|
+
` Run ${pc8.cyan("flydocs")} first to install FlyDocs in this project.`
|
|
2382
|
+
);
|
|
2383
|
+
process.exit(1);
|
|
2384
|
+
}
|
|
2385
|
+
const config = await readConfig(targetDir);
|
|
2386
|
+
if (config.tier === "cloud") {
|
|
2387
|
+
printInfo("This project is already connected to the cloud tier.");
|
|
2388
|
+
console.log();
|
|
2389
|
+
const reconnect = await confirm3({
|
|
2390
|
+
message: "Want to update your API key?"
|
|
2391
|
+
});
|
|
2392
|
+
if (isCancel5(reconnect) || !reconnect) {
|
|
2393
|
+
console.log(` No changes made.`);
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
console.log();
|
|
2398
|
+
console.log(` ${pc8.bold("Connect to Linear")}`);
|
|
2399
|
+
console.log();
|
|
2400
|
+
console.log(
|
|
2401
|
+
` ${pc8.dim("Get your API key from: Linear \u2192 Settings \u2192 API \u2192 Personal API keys")}`
|
|
2402
|
+
);
|
|
2403
|
+
console.log();
|
|
2404
|
+
let apiKey = args.key ?? "";
|
|
2405
|
+
if (!apiKey) {
|
|
2406
|
+
const keyInput = await text2({
|
|
2407
|
+
message: "Enter your Linear API key",
|
|
2408
|
+
placeholder: "lin_api_...",
|
|
2409
|
+
validate(value) {
|
|
2410
|
+
if (!value.trim()) return "API key is required";
|
|
2411
|
+
if (!value.startsWith("lin_api_"))
|
|
2412
|
+
return "Linear API keys start with lin_api_";
|
|
2413
|
+
return void 0;
|
|
2414
|
+
}
|
|
2415
|
+
});
|
|
2416
|
+
if (isCancel5(keyInput)) {
|
|
2417
|
+
cancel4("Connection cancelled.");
|
|
2418
|
+
process.exit(0);
|
|
2419
|
+
}
|
|
2420
|
+
apiKey = keyInput;
|
|
2421
|
+
}
|
|
2422
|
+
printInfo("Validating API key...");
|
|
2423
|
+
try {
|
|
2424
|
+
const result = execSync2(
|
|
2425
|
+
`python3 -c "
|
|
2426
|
+
import urllib.request, json
|
|
2427
|
+
req = urllib.request.Request('https://api.linear.app/graphql',
|
|
2428
|
+
data=json.dumps({'query': '{ viewer { id name email } }'}).encode(),
|
|
2429
|
+
headers={'Authorization': '${apiKey}', 'Content-Type': 'application/json'})
|
|
2430
|
+
res = urllib.request.urlopen(req)
|
|
2431
|
+
data = json.loads(res.read())
|
|
2432
|
+
print(json.dumps(data['data']['viewer']))
|
|
2433
|
+
"`,
|
|
2434
|
+
{ encoding: "utf-8", timeout: 15e3 }
|
|
2435
|
+
);
|
|
2436
|
+
const viewer = JSON.parse(result.trim());
|
|
2437
|
+
printStatus(`Authenticated as ${pc8.bold(viewer.name)} (${viewer.email})`);
|
|
2438
|
+
} catch {
|
|
2439
|
+
printError("Invalid API key or network error.");
|
|
2440
|
+
console.log(` Check your key and try again.`);
|
|
2441
|
+
process.exit(1);
|
|
2442
|
+
}
|
|
2443
|
+
const envPath = join13(targetDir, ".env");
|
|
2444
|
+
const envLocalPath = join13(targetDir, ".env.local");
|
|
2445
|
+
const targetEnvPath = await pathExists(envLocalPath) ? envLocalPath : envPath;
|
|
2446
|
+
if (await pathExists(targetEnvPath)) {
|
|
2447
|
+
const envContent = await readFile9(targetEnvPath, "utf-8");
|
|
2448
|
+
if (envContent.includes("LINEAR_API_KEY=")) {
|
|
2449
|
+
const updated = envContent.replace(
|
|
2450
|
+
/LINEAR_API_KEY=.*/,
|
|
2451
|
+
`LINEAR_API_KEY=${apiKey}`
|
|
2452
|
+
);
|
|
2453
|
+
await writeFile5(targetEnvPath, updated, "utf-8");
|
|
2454
|
+
} else {
|
|
2455
|
+
await appendFile2(targetEnvPath, `
|
|
2456
|
+
LINEAR_API_KEY=${apiKey}
|
|
2457
|
+
`);
|
|
2458
|
+
}
|
|
2459
|
+
} else {
|
|
2460
|
+
await writeFile5(targetEnvPath, `LINEAR_API_KEY=${apiKey}
|
|
2461
|
+
`, "utf-8");
|
|
2462
|
+
}
|
|
2463
|
+
printStatus(
|
|
2464
|
+
`API key stored in ${pc8.dim(targetEnvPath === envLocalPath ? ".env.local" : ".env")}`
|
|
2465
|
+
);
|
|
2466
|
+
const wasLocal = config.tier === "local";
|
|
2467
|
+
config.tier = "cloud";
|
|
2468
|
+
config.provider = config.provider ?? { type: "linear", teamId: null };
|
|
2469
|
+
config.provider.type = "linear";
|
|
2470
|
+
await writeConfig(targetDir, config);
|
|
2471
|
+
printStatus("Config updated to cloud tier");
|
|
2472
|
+
if (wasLocal) {
|
|
2473
|
+
try {
|
|
2474
|
+
const templateDir = await resolveTemplatePath(
|
|
2475
|
+
args["local-source"] || void 0
|
|
2476
|
+
);
|
|
2477
|
+
const templateSkillsDir = join13(templateDir, ".claude", "skills");
|
|
2478
|
+
const skillsDir = join13(targetDir, ".claude", "skills");
|
|
2479
|
+
await replaceDirectory(
|
|
2480
|
+
join13(templateSkillsDir, "flydocs-cloud"),
|
|
2481
|
+
join13(skillsDir, "flydocs-cloud")
|
|
2482
|
+
);
|
|
2483
|
+
const { rm: rm5 } = await import("fs/promises");
|
|
2484
|
+
const localSkillDir = join13(skillsDir, "flydocs-local");
|
|
2485
|
+
if (await pathExists(localSkillDir)) {
|
|
2486
|
+
await rm5(localSkillDir, { recursive: true, force: true });
|
|
2487
|
+
}
|
|
2488
|
+
printStatus("Cloud mechanism skill installed");
|
|
2489
|
+
} catch {
|
|
2490
|
+
printInfo(
|
|
2491
|
+
"Could not swap mechanism skills automatically. Run flydocs update to complete."
|
|
2492
|
+
);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
console.log();
|
|
2496
|
+
console.log(
|
|
2497
|
+
` ${pc8.bold("Connected!")} Your project now syncs with Linear.`
|
|
2498
|
+
);
|
|
2499
|
+
console.log();
|
|
2500
|
+
console.log(` Next steps:`);
|
|
2501
|
+
console.log(
|
|
2502
|
+
` 1. Run ${pc8.cyan("/flydocs-setup")} in your IDE to configure your Linear project`
|
|
2503
|
+
);
|
|
2504
|
+
console.log(` 2. Run ${pc8.cyan("/start-session")} to begin working`);
|
|
2505
|
+
console.log();
|
|
2506
|
+
}
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
});
|
|
2510
|
+
|
|
2511
|
+
// src/commands/upgrade.ts
|
|
2512
|
+
var upgrade_exports = {};
|
|
2513
|
+
__export(upgrade_exports, {
|
|
2514
|
+
default: () => upgrade_default
|
|
2515
|
+
});
|
|
2516
|
+
import { defineCommand as defineCommand6 } from "citty";
|
|
2517
|
+
import pc9 from "picocolors";
|
|
2518
|
+
var upgrade_default;
|
|
2519
|
+
var init_upgrade = __esm({
|
|
2520
|
+
"src/commands/upgrade.ts"() {
|
|
2521
|
+
"use strict";
|
|
2522
|
+
init_config();
|
|
2523
|
+
init_fs_ops();
|
|
2524
|
+
upgrade_default = defineCommand6({
|
|
2525
|
+
meta: {
|
|
2526
|
+
name: "upgrade",
|
|
2527
|
+
description: "Learn about FlyDocs Cloud tier and upgrade from local"
|
|
2528
|
+
},
|
|
2529
|
+
args: {
|
|
2530
|
+
path: {
|
|
2531
|
+
type: "string",
|
|
2532
|
+
description: "Path to FlyDocs project"
|
|
2533
|
+
},
|
|
2534
|
+
here: {
|
|
2535
|
+
type: "boolean",
|
|
2536
|
+
description: "Use current directory"
|
|
2537
|
+
}
|
|
2538
|
+
},
|
|
2539
|
+
async run({ args }) {
|
|
2540
|
+
const targetDir = args.path ?? (args.here ? process.cwd() : process.cwd());
|
|
2541
|
+
const configExists = await pathExists(`${targetDir}/.flydocs/config.json`);
|
|
2542
|
+
let currentTier = "unknown";
|
|
2543
|
+
if (configExists) {
|
|
2544
|
+
try {
|
|
2545
|
+
const config = await readConfig(targetDir);
|
|
2546
|
+
currentTier = config.tier;
|
|
2547
|
+
} catch {
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
console.log();
|
|
2551
|
+
if (currentTier === "cloud") {
|
|
2552
|
+
console.log(
|
|
2553
|
+
` ${pc9.green("\u2713")} You're already on the ${pc9.bold("cloud")} tier.`
|
|
2554
|
+
);
|
|
2555
|
+
console.log();
|
|
2556
|
+
console.log(
|
|
2557
|
+
` Your issues sync with Linear via the cloud mechanism skill.`
|
|
2558
|
+
);
|
|
2559
|
+
console.log(
|
|
2560
|
+
` Run ${pc9.cyan("flydocs connect")} to update your connection settings.`
|
|
2561
|
+
);
|
|
2562
|
+
console.log();
|
|
2563
|
+
return;
|
|
2564
|
+
}
|
|
2565
|
+
console.log(` ${pc9.bold("FlyDocs Cloud Tier")}`);
|
|
2566
|
+
console.log();
|
|
2567
|
+
console.log(` You're currently on the ${pc9.yellow("local")} tier.`);
|
|
2568
|
+
console.log(` Upgrade to cloud for:`);
|
|
2569
|
+
console.log();
|
|
2570
|
+
console.log(
|
|
2571
|
+
` ${pc9.cyan("\u2192")} Issue sync with Linear (Jira coming soon)`
|
|
2572
|
+
);
|
|
2573
|
+
console.log(` ${pc9.cyan("\u2192")} Project milestones and cycle management`);
|
|
2574
|
+
console.log(` ${pc9.cyan("\u2192")} Team assignment and priority tracking`);
|
|
2575
|
+
console.log(` ${pc9.cyan("\u2192")} Project health updates and dashboards`);
|
|
2576
|
+
console.log(` ${pc9.cyan("\u2192")} Cross-project issue linking`);
|
|
2577
|
+
console.log();
|
|
2578
|
+
console.log(` ${pc9.bold("How to upgrade:")}`);
|
|
2579
|
+
console.log();
|
|
2580
|
+
console.log(` 1. Sign up at ${pc9.cyan("https://www.flydocs.ai")}`);
|
|
2581
|
+
console.log(` 2. Get your Linear API key from Linear \u2192 Settings \u2192 API`);
|
|
2582
|
+
console.log(
|
|
2583
|
+
` 3. Run ${pc9.cyan("flydocs connect")} to connect your project`
|
|
2584
|
+
);
|
|
2585
|
+
console.log();
|
|
2586
|
+
}
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
});
|
|
2590
|
+
|
|
2591
|
+
// src/commands/self-update.ts
|
|
2592
|
+
var self_update_exports = {};
|
|
2593
|
+
__export(self_update_exports, {
|
|
2594
|
+
default: () => self_update_default
|
|
2595
|
+
});
|
|
2596
|
+
import { defineCommand as defineCommand7 } from "citty";
|
|
2597
|
+
import { execSync as execSync3 } from "child_process";
|
|
2598
|
+
import pc10 from "picocolors";
|
|
2599
|
+
var self_update_default;
|
|
2600
|
+
var init_self_update = __esm({
|
|
2601
|
+
"src/commands/self-update.ts"() {
|
|
2602
|
+
"use strict";
|
|
2603
|
+
init_constants();
|
|
2604
|
+
init_ui();
|
|
2605
|
+
self_update_default = defineCommand7({
|
|
2606
|
+
meta: {
|
|
2607
|
+
name: "self-update",
|
|
2608
|
+
description: "Update FlyDocs CLI to the latest version"
|
|
2609
|
+
},
|
|
2610
|
+
async run() {
|
|
2611
|
+
console.log();
|
|
2612
|
+
console.log(` Updating ${pc10.cyan(PACKAGE_NAME)}...`);
|
|
2613
|
+
console.log();
|
|
2614
|
+
try {
|
|
2615
|
+
execSync3(`npm update -g ${PACKAGE_NAME}`, {
|
|
2616
|
+
stdio: "inherit",
|
|
2617
|
+
timeout: 6e4
|
|
2618
|
+
});
|
|
2619
|
+
console.log();
|
|
2620
|
+
printStatus("FlyDocs CLI updated successfully");
|
|
2621
|
+
} catch {
|
|
2622
|
+
printError(`Update failed. Try manually: npm update -g ${PACKAGE_NAME}`);
|
|
2623
|
+
process.exit(1);
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
});
|
|
2629
|
+
|
|
2630
|
+
// src/cli.ts
|
|
2631
|
+
init_constants();
|
|
2632
|
+
import { defineCommand as defineCommand8, runCommand, runMain } from "citty";
|
|
2633
|
+
var main = defineCommand8({
|
|
2634
|
+
meta: {
|
|
2635
|
+
name: CLI_NAME,
|
|
2636
|
+
version: CLI_VERSION,
|
|
2637
|
+
description: "FlyDocs AI CLI \u2014 install, setup, and manage FlyDocs projects"
|
|
2638
|
+
},
|
|
2639
|
+
subCommands: {
|
|
2640
|
+
install: () => Promise.resolve().then(() => (init_install(), install_exports)).then((m) => m.default),
|
|
2641
|
+
update: () => Promise.resolve().then(() => (init_update(), update_exports)).then((m) => m.default),
|
|
2642
|
+
setup: () => Promise.resolve().then(() => (init_setup(), setup_exports)).then((m) => m.default),
|
|
2643
|
+
skills: () => Promise.resolve().then(() => (init_skills2(), skills_exports)).then((m) => m.default),
|
|
2644
|
+
connect: () => Promise.resolve().then(() => (init_connect(), connect_exports)).then((m) => m.default),
|
|
2645
|
+
upgrade: () => Promise.resolve().then(() => (init_upgrade(), upgrade_exports)).then((m) => m.default),
|
|
2646
|
+
"self-update": () => Promise.resolve().then(() => (init_self_update(), self_update_exports)).then((m) => m.default)
|
|
2647
|
+
},
|
|
2648
|
+
async run({ rawArgs }) {
|
|
2649
|
+
const subCommandNames = /* @__PURE__ */ new Set([
|
|
2650
|
+
"install",
|
|
2651
|
+
"update",
|
|
2652
|
+
"setup",
|
|
2653
|
+
"skills",
|
|
2654
|
+
"connect",
|
|
2655
|
+
"upgrade",
|
|
2656
|
+
"self-update"
|
|
2657
|
+
]);
|
|
2658
|
+
const firstArg = rawArgs.find((a) => !a.startsWith("-"));
|
|
2659
|
+
if (firstArg && subCommandNames.has(firstArg)) return;
|
|
2660
|
+
const installCmd = await Promise.resolve().then(() => (init_install(), install_exports)).then(
|
|
2661
|
+
(m) => m.default
|
|
2662
|
+
);
|
|
2663
|
+
await runCommand(installCmd, { rawArgs });
|
|
2664
|
+
}
|
|
2665
|
+
});
|
|
2666
|
+
runMain(main);
|