@sylphx/flow 1.0.0 → 1.0.1

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.
@@ -0,0 +1,61 @@
1
+ import {
2
+ pathSecurity
3
+ } from "./chunk-hpkhykhq.js";
4
+
5
+ // src/utils/paths.ts
6
+ import fs from "node:fs";
7
+ import path from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ function findPackageRoot(context) {
10
+ const __filename2 = fileURLToPath(import.meta.url);
11
+ let currentDir = path.dirname(__filename2);
12
+ for (let i = 0;i < 10; i++) {
13
+ const packageJsonPath = path.join(currentDir, "package.json");
14
+ if (fs.existsSync(packageJsonPath)) {
15
+ return currentDir;
16
+ }
17
+ const parentDir = path.dirname(currentDir);
18
+ if (parentDir === currentDir)
19
+ break;
20
+ currentDir = parentDir;
21
+ }
22
+ const errorMsg = context ? `Cannot find package.json - ${context} location unknown` : "Cannot find package.json";
23
+ throw new Error(errorMsg);
24
+ }
25
+ var PACKAGE_ROOT = findPackageRoot();
26
+ var MONOREPO_ROOT = path.join(PACKAGE_ROOT, "..", "..");
27
+ var ASSETS_ROOT = fs.existsSync(path.join(MONOREPO_ROOT, "assets")) ? path.join(MONOREPO_ROOT, "assets") : path.join(PACKAGE_ROOT, "assets");
28
+ function getAgentsDir() {
29
+ return path.join(ASSETS_ROOT, "agents");
30
+ }
31
+ function getRulesDir() {
32
+ return path.join(ASSETS_ROOT, "rules");
33
+ }
34
+ function getKnowledgeDir() {
35
+ return path.join(ASSETS_ROOT, "knowledge");
36
+ }
37
+ function getOutputStylesDir() {
38
+ return path.join(ASSETS_ROOT, "output-styles");
39
+ }
40
+ function getSlashCommandsDir() {
41
+ return path.join(ASSETS_ROOT, "slash-commands");
42
+ }
43
+ function getRuleFile(filename) {
44
+ if (!filename || typeof filename !== "string") {
45
+ throw new Error("Filename must be a non-empty string");
46
+ }
47
+ if (filename.includes("..") || filename.includes("/") || filename.includes("\\")) {
48
+ throw new Error(`Invalid filename: ${filename}. Path traversal not allowed.`);
49
+ }
50
+ if (!/^[a-zA-Z0-9._-]+$/.test(filename)) {
51
+ throw new Error(`Filename contains invalid characters: ${filename}`);
52
+ }
53
+ const rulesDir = getRulesDir();
54
+ const filePath = pathSecurity.safeJoin(rulesDir, filename);
55
+ if (!fs.existsSync(filePath)) {
56
+ throw new Error(`Rule file not found: ${filename} (looked in ${rulesDir})`);
57
+ }
58
+ return filePath;
59
+ }
60
+
61
+ export { getAgentsDir, getRulesDir, getKnowledgeDir, getOutputStylesDir, getSlashCommandsDir, getRuleFile };
@@ -0,0 +1,19 @@
1
+ // src/core/functional/error-types.ts
2
+ var configError = (message, options) => ({
3
+ kind: "ConfigError",
4
+ message,
5
+ configKey: options?.configKey,
6
+ configPath: options?.configPath,
7
+ context: options?.context,
8
+ cause: options?.cause
9
+ });
10
+ var fileSystemError = (message, path, operation, options) => ({
11
+ kind: "FileSystemError",
12
+ message,
13
+ path,
14
+ operation,
15
+ context: options?.context,
16
+ cause: options?.cause
17
+ });
18
+
19
+ export { configError, fileSystemError };
@@ -0,0 +1,276 @@
1
+ import {
2
+ ConfigService
3
+ } from "./chunk-x66eh37x.js";
4
+ import {
5
+ __require,
6
+ __toESM
7
+ } from "./chunk-5j4w74t6.js";
8
+
9
+ // src/core/state-detector.ts
10
+ import fs from "node:fs/promises";
11
+ import path from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ var __filename2 = fileURLToPath(import.meta.url);
14
+ var __dirname2 = path.dirname(__filename2);
15
+
16
+ class StateDetector {
17
+ projectPath;
18
+ constructor(projectPath = process.cwd()) {
19
+ this.projectPath = projectPath;
20
+ }
21
+ async detect() {
22
+ const state = {
23
+ initialized: false,
24
+ version: null,
25
+ latestVersion: await this.getLatestFlowVersion(),
26
+ target: null,
27
+ targetVersion: null,
28
+ targetLatestVersion: null,
29
+ components: {
30
+ agents: { installed: false, count: 0, version: null },
31
+ rules: { installed: false, count: 0, version: null },
32
+ hooks: { installed: false, version: null },
33
+ mcp: { installed: false, serverCount: 0, version: null },
34
+ outputStyles: { installed: false, version: null },
35
+ slashCommands: { installed: false, count: 0, version: null }
36
+ },
37
+ corrupted: false,
38
+ outdated: false,
39
+ lastUpdated: null
40
+ };
41
+ try {
42
+ state.initialized = await ConfigService.isInitialized(this.projectPath);
43
+ if (!state.initialized) {
44
+ return state;
45
+ }
46
+ const config = await ConfigService.loadProjectSettings(this.projectPath);
47
+ state.version = config.version || null;
48
+ state.target = config.target || null;
49
+ state.lastUpdated = config.lastUpdated ? new Date(config.lastUpdated) : null;
50
+ if (state.version && state.latestVersion) {
51
+ state.outdated = this.isVersionOutdated(state.version, state.latestVersion);
52
+ }
53
+ if (state.target === "opencode") {
54
+ await this.checkComponent("agents", ".opencode/agent", "*.md", state);
55
+ await this.checkFileComponent("rules", "AGENTS.md", state);
56
+ state.components.hooks.installed = false;
57
+ state.components.outputStyles.installed = await this.checkOutputStylesInAGENTS();
58
+ await this.checkComponent("slashCommands", ".opencode/command", "*.md", state);
59
+ } else {
60
+ await this.checkComponent("agents", ".claude/agents", "*.md", state);
61
+ await this.checkComponent("rules", ".claude/rules", "*.md", state);
62
+ await this.checkComponent("hooks", ".claude/hooks", "*.js", state);
63
+ await this.checkComponent("outputStyles", ".claude/output-styles", "*.md", state);
64
+ await this.checkComponent("slashCommands", ".claude/commands", "*.md", state);
65
+ }
66
+ const mcpConfig = await this.checkMCPConfig(state.target);
67
+ state.components.mcp.installed = mcpConfig.exists;
68
+ state.components.mcp.serverCount = mcpConfig.serverCount;
69
+ state.components.mcp.version = mcpConfig.version;
70
+ if (state.target) {
71
+ const targetInfo = await this.checkTargetVersion(state.target);
72
+ state.targetVersion = targetInfo.version;
73
+ state.targetLatestVersion = targetInfo.latestVersion;
74
+ }
75
+ state.corrupted = await this.checkCorruption(state);
76
+ } catch (error) {
77
+ state.corrupted = true;
78
+ }
79
+ return state;
80
+ }
81
+ recommendAction(state) {
82
+ if (!state.initialized) {
83
+ return "FULL_INIT";
84
+ }
85
+ if (state.corrupted) {
86
+ return "REPAIR";
87
+ }
88
+ if (state.outdated && state.version !== state.latestVersion) {
89
+ return "UPGRADE";
90
+ }
91
+ if (state.targetVersion && state.targetLatestVersion && this.isVersionOutdated(state.targetVersion, state.targetLatestVersion)) {
92
+ return "UPGRADE_TARGET";
93
+ }
94
+ return "RUN_ONLY";
95
+ }
96
+ async explainState(state) {
97
+ const explanations = [];
98
+ if (!state.initialized) {
99
+ explanations.push("Project not initialized yet");
100
+ explanations.push("Run `bun dev:flow` to start initialization");
101
+ return explanations;
102
+ }
103
+ if (state.corrupted) {
104
+ explanations.push("Configuration corruption detected");
105
+ explanations.push("Run `bun dev:flow --clean` to repair");
106
+ return explanations;
107
+ }
108
+ if (state.outdated) {
109
+ explanations.push(`Flow version outdated: ${state.version} → ${state.latestVersion}`);
110
+ explanations.push("Run `bun dev:flow upgrade` to upgrade");
111
+ }
112
+ if (state.targetVersion && state.targetLatestVersion && this.isVersionOutdated(state.targetVersion, state.targetLatestVersion)) {
113
+ explanations.push(`${state.target} update available`);
114
+ explanations.push(`Run \`bun dev:flow upgrade-target\` to upgrade`);
115
+ }
116
+ Object.entries(state.components).forEach(([name, component]) => {
117
+ if (!component.installed) {
118
+ explanations.push(`Missing ${name}`);
119
+ }
120
+ });
121
+ if (explanations.length === 0) {
122
+ explanations.push("Project status is normal");
123
+ explanations.push("Run `bun dev:flow` to start Claude Code");
124
+ }
125
+ return explanations;
126
+ }
127
+ async getLatestFlowVersion() {
128
+ try {
129
+ const packagePath = path.join(__dirname2, "..", "..", "package.json");
130
+ const packageJson = JSON.parse(await fs.readFile(packagePath, "utf-8"));
131
+ return packageJson.version || null;
132
+ } catch {
133
+ return null;
134
+ }
135
+ }
136
+ async checkComponent(componentName, componentPath, pattern, state) {
137
+ try {
138
+ const fullPath = path.join(this.projectPath, componentPath);
139
+ const exists = await fs.access(fullPath).then(() => true).catch(() => false);
140
+ state.components[componentName].installed = exists;
141
+ if (exists) {
142
+ const files = await fs.readdir(fullPath).catch(() => []);
143
+ const count = pattern === "*.js" ? files.filter((f) => f.endsWith(".js")).length : pattern === "*.md" ? files.filter((f) => f.endsWith(".md")).length : files.length;
144
+ if (componentName === "agents" || componentName === "slashCommands" || componentName === "rules") {
145
+ state.components[componentName].count = count;
146
+ }
147
+ const versionPath = path.join(fullPath, ".version");
148
+ const versionExists = await fs.access(versionPath).then(() => true).catch(() => false);
149
+ if (versionExists) {
150
+ state.components[componentName].version = await fs.readFile(versionPath, "utf-8");
151
+ }
152
+ }
153
+ } catch {
154
+ state.components[componentName].installed = false;
155
+ }
156
+ }
157
+ async checkFileComponent(componentName, filePath, state) {
158
+ try {
159
+ const fullPath = path.join(this.projectPath, filePath);
160
+ const exists = await fs.access(fullPath).then(() => true).catch(() => false);
161
+ state.components[componentName].installed = exists;
162
+ if (exists && componentName === "rules") {
163
+ state.components[componentName].count = 1;
164
+ }
165
+ } catch {
166
+ state.components[componentName].installed = false;
167
+ }
168
+ }
169
+ async checkOutputStylesInAGENTS() {
170
+ try {
171
+ const agentsPath = path.join(this.projectPath, "AGENTS.md");
172
+ const exists = await fs.access(agentsPath).then(() => true).catch(() => false);
173
+ if (!exists) {
174
+ return false;
175
+ }
176
+ const content = await fs.readFile(agentsPath, "utf-8");
177
+ return content.includes("# Output Styles");
178
+ } catch {
179
+ return false;
180
+ }
181
+ }
182
+ async checkMCPConfig(target) {
183
+ try {
184
+ let mcpPath;
185
+ let serversKey;
186
+ if (target === "opencode") {
187
+ mcpPath = path.join(this.projectPath, "opencode.jsonc");
188
+ serversKey = "mcp";
189
+ } else {
190
+ mcpPath = path.join(this.projectPath, ".mcp.json");
191
+ serversKey = "mcpServers";
192
+ }
193
+ const exists = await fs.access(mcpPath).then(() => true).catch(() => false);
194
+ if (!exists) {
195
+ return { exists: false, serverCount: 0, version: null };
196
+ }
197
+ let content;
198
+ if (target === "opencode") {
199
+ const { fileUtils } = await import("./chunk-hpkhykhq.js");
200
+ const { opencodeTarget } = await import("./chunk-cjd3mk4c.js");
201
+ content = await fileUtils.readConfig(opencodeTarget.config, this.projectPath);
202
+ } else {
203
+ content = JSON.parse(await fs.readFile(mcpPath, "utf-8"));
204
+ }
205
+ const servers = content[serversKey] || {};
206
+ return {
207
+ exists: true,
208
+ serverCount: Object.keys(servers).length,
209
+ version: content.version || null
210
+ };
211
+ } catch {
212
+ return { exists: false, serverCount: 0, version: null };
213
+ }
214
+ }
215
+ async checkTargetVersion(target) {
216
+ try {
217
+ if (target === "claude-code") {
218
+ const { exec } = await import("node:child_process");
219
+ const { promisify } = await import("node:util");
220
+ const execAsync = promisify(exec);
221
+ try {
222
+ const { stdout } = await execAsync("claude --version");
223
+ const match = stdout.match(/v?(\d+\.\d+\.\d+)/);
224
+ return {
225
+ version: match ? match[1] : null,
226
+ latestVersion: await this.getLatestClaudeVersion()
227
+ };
228
+ } catch {
229
+ return { version: null, latestVersion: null };
230
+ }
231
+ }
232
+ return { version: null, latestVersion: null };
233
+ } catch {
234
+ return { version: null, latestVersion: null };
235
+ }
236
+ }
237
+ async getLatestClaudeVersion() {
238
+ try {
239
+ const { exec } = await import("node:child_process");
240
+ const { promisify } = await import("node:util");
241
+ const execAsync = promisify(exec);
242
+ const { stdout } = await execAsync("npm view @anthropic-ai/claude-code version");
243
+ return stdout.trim();
244
+ } catch {
245
+ return null;
246
+ }
247
+ }
248
+ async checkCorruption(state) {
249
+ if (state.initialized && !state.target) {
250
+ return true;
251
+ }
252
+ if (state.initialized && state.target === "claude-code" && !state.components.agents.installed) {
253
+ return true;
254
+ }
255
+ return false;
256
+ }
257
+ isVersionOutdated(current, latest) {
258
+ try {
259
+ return this.compareVersions(current, latest) < 0;
260
+ } catch {
261
+ return false;
262
+ }
263
+ }
264
+ compareVersions(v1, v2) {
265
+ const parts1 = v1.split(".").map(Number);
266
+ const parts2 = v2.split(".").map(Number);
267
+ for (let i = 0;i < Math.min(parts1.length, parts2.length); i++) {
268
+ if (parts1[i] !== parts2[i]) {
269
+ return parts1[i] - parts2[i];
270
+ }
271
+ }
272
+ return parts1.length - parts2.length;
273
+ }
274
+ }
275
+
276
+ export { StateDetector };
@@ -0,0 +1,71 @@
1
+ import {
2
+ installComponents,
3
+ previewDryRun,
4
+ selectAndValidateTarget
5
+ } from "./chunk-6g9xy73m.js";
6
+ import"./chunk-x66eh37x.js";
7
+ import"./chunk-7eq34c42.js";
8
+ import"./chunk-vmeqwm1c.js";
9
+ import"./chunk-qk8n91hw.js";
10
+ import"./chunk-nd5fdvaq.js";
11
+ import"./chunk-cjd3mk4c.js";
12
+ import"./chunk-t16rfxh0.js";
13
+ import"./chunk-hpkhykhq.js";
14
+ import"./chunk-5j4w74t6.js";
15
+
16
+ // src/commands/init-command.ts
17
+ import boxen from "boxen";
18
+ import chalk from "chalk";
19
+ import gradient from "gradient-string";
20
+ async function runInit(options) {
21
+ const title = `
22
+ ███████╗██╗ ██╗██╗ ██████╗ ██╗ ██╗██╗ ██╗ ███████╗██╗ ██████╗ ██╗ ██╗
23
+ ██╔════╝╚██╗ ██╔╝██║ ██╔══██╗██║ ██║╚██╗██╔╝ ██╔════╝██║ ██╔═══██╗██║ ██║
24
+ ███████╗ ╚████╔╝ ██║ ██████╔╝███████║ ╚███╔╝ █████╗ ██║ ██║ ██║██║ █╗ ██║
25
+ ╚════██║ ╚██╔╝ ██║ ██╔═══╝ ██╔══██║ ██╔██╗ ██╔══╝ ██║ ██║ ██║██║███╗██║
26
+ ███████║ ██║ ███████╗██║ ██║ ██║██╔╝ ██╗ ██║ ███████╗╚██████╔╝╚███╔███╔╝
27
+ ╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═════╝ ╚══╝╚══╝
28
+ `;
29
+ console.log(gradient(["cyan", "blue"])(title));
30
+ console.log(chalk.dim.cyan(` Project Initialization
31
+ `));
32
+ const targetId = await selectAndValidateTarget(options);
33
+ if (options.dryRun) {
34
+ console.log(boxen(chalk.yellow("⚠ Dry Run Mode") + chalk.dim(`
35
+ No changes will be made to your project`), {
36
+ padding: 1,
37
+ margin: { top: 0, bottom: 1, left: 0, right: 0 },
38
+ borderStyle: "round",
39
+ borderColor: "yellow"
40
+ }));
41
+ await previewDryRun(targetId, options);
42
+ console.log(`
43
+ ` + boxen(chalk.green.bold("✓ Dry run complete"), {
44
+ padding: { top: 0, bottom: 0, left: 2, right: 2 },
45
+ margin: 0,
46
+ borderStyle: "round",
47
+ borderColor: "green"
48
+ }) + `
49
+ `);
50
+ return;
51
+ }
52
+ console.log(chalk.cyan.bold(`
53
+ ━━━ Installing Core Components ━━━
54
+ `));
55
+ const result = await installComponents(targetId, options);
56
+ console.log(`
57
+ ` + boxen(chalk.green.bold("✓ Setup complete!") + `
58
+
59
+ ` + chalk.dim(`Target: ${result.targetName}`) + `
60
+
61
+ ` + chalk.cyan("Ready to code with Sylphx Flow"), {
62
+ padding: 1,
63
+ margin: 0,
64
+ borderStyle: "round",
65
+ borderColor: "green"
66
+ }) + `
67
+ `);
68
+ }
69
+ export {
70
+ runInit
71
+ };