flutter-pro-max-cli 1.0.2 → 2.1.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 +109 -16
- package/assets/data/flutter-performance.csv +36 -0
- package/assets/data/mobile-accessibility.csv +36 -0
- package/assets/data/ui-reasoning.csv +36 -0
- package/assets/scripts/__pycache__/core.cpython-312.pyc +0 -0
- package/assets/scripts/__pycache__/design_system.cpython-312.pyc +0 -0
- package/assets/{.claude/skills/flutter-pro-max/scripts → scripts}/core.py +72 -48
- package/assets/scripts/design_system.py +1074 -0
- package/assets/{.codebuddy/commands/scripts → scripts}/search.py +73 -2
- package/assets/templates/base/quick-reference.md +41 -0
- package/assets/templates/base/skill-content.md +179 -0
- package/assets/templates/platforms/agent.json +21 -0
- package/assets/templates/platforms/claude.json +21 -0
- package/assets/templates/platforms/codebuddy.json +21 -0
- package/assets/templates/platforms/codex.json +21 -0
- package/assets/templates/platforms/continue.json +21 -0
- package/assets/templates/platforms/copilot.json +18 -0
- package/assets/templates/platforms/cursor.json +18 -0
- package/assets/templates/platforms/gemini.json +21 -0
- package/assets/templates/platforms/kiro.json +18 -0
- package/assets/templates/platforms/opencode.json +21 -0
- package/assets/templates/platforms/qoder.json +21 -0
- package/assets/templates/platforms/roocode.json +18 -0
- package/assets/templates/platforms/trae.json +21 -0
- package/assets/templates/platforms/windsurf.json +18 -0
- package/dist/commands/init.js +13 -13
- package/dist/commands/update.d.ts +6 -0
- package/dist/commands/update.js +27 -0
- package/dist/commands/versions.d.ts +1 -0
- package/dist/commands/versions.js +36 -0
- package/dist/index.js +27 -1
- package/dist/types/index.d.ts +20 -1
- package/dist/types/index.js +4 -1
- package/dist/utils/detect.js +11 -1
- package/dist/utils/extract.d.ts +5 -0
- package/dist/utils/github.d.ts +11 -0
- package/dist/utils/github.js +81 -0
- package/dist/utils/template.d.ts +25 -0
- package/dist/utils/template.js +194 -0
- package/package.json +8 -4
- package/assets/.agent/workflows/flutter-pro-max.md +0 -221
- package/assets/.agent/workflows/scripts/core.py +0 -345
- package/assets/.agent/workflows/scripts/search.py +0 -106
- package/assets/.claude/skills/flutter-pro-max/SKILL.md +0 -339
- package/assets/.claude/skills/flutter-pro-max/scripts/search.py +0 -106
- package/assets/.codebuddy/commands/flutter-pro-max.md +0 -221
- package/assets/.codebuddy/commands/scripts/core.py +0 -345
- package/assets/.codex/skills/flutter-pro-max/SKILL.md +0 -221
- package/assets/.codex/skills/flutter-pro-max/scripts/core.py +0 -345
- package/assets/.codex/skills/flutter-pro-max/scripts/search.py +0 -106
- package/assets/.cursor/commands/flutter-pro-max.md +0 -221
- package/assets/.cursor/commands/scripts/core.py +0 -345
- package/assets/.cursor/commands/scripts/search.py +0 -106
- package/assets/.gemini/skills/flutter-pro-max/SKILL.md +0 -221
- package/assets/.gemini/skills/flutter-pro-max/scripts/core.py +0 -345
- package/assets/.gemini/skills/flutter-pro-max/scripts/search.py +0 -106
- package/assets/.github/prompts/flutter-pro-max.prompt.md +0 -221
- package/assets/.github/prompts/scripts/core.py +0 -345
- package/assets/.github/prompts/scripts/search.py +0 -106
- package/assets/.kiro/steering/flutter-pro-max.md +0 -220
- package/assets/.kiro/steering/scripts/core.py +0 -345
- package/assets/.kiro/steering/scripts/search.py +0 -106
- package/assets/.qoder/rules/flutter-pro-max.md +0 -220
- package/assets/.qoder/rules/scripts/core.py +0 -345
- package/assets/.qoder/rules/scripts/search.py +0 -106
- package/assets/.roo/commands/flutter-pro-max.md +0 -220
- package/assets/.roo/commands/scripts/core.py +0 -345
- package/assets/.roo/commands/scripts/search.py +0 -106
- package/assets/.shared/flutter-pro-max/SKILL.md +0 -221
- package/assets/.shared/flutter-pro-max/scripts/core.py +0 -341
- package/assets/.shared/flutter-pro-max/scripts/search.py +0 -106
- package/assets/.trae/skills/flutter-pro-max/SKILL.md +0 -221
- package/assets/.trae/skills/flutter-pro-max/scripts/core.py +0 -345
- package/assets/.trae/skills/flutter-pro-max/scripts/search.py +0 -106
- package/assets/.windsurf/workflows/flutter-pro-max.md +0 -221
- package/assets/.windsurf/workflows/scripts/core.py +0 -345
- package/assets/.windsurf/workflows/scripts/search.py +0 -106
- package/dist/utils/extract.js +0 -83
- /package/assets/{.shared/data → data}/architect.csv +0 -0
- /package/assets/{.shared/data → data}/charts.csv +0 -0
- /package/assets/{.shared/data → data}/colors.csv +0 -0
- /package/assets/{.shared/data → data}/icons.csv +0 -0
- /package/assets/{.shared/data → data}/landing.csv +0 -0
- /package/assets/{.shared/data → data}/name_convention.csv +0 -0
- /package/assets/{.shared/data → data}/package.csv +0 -0
- /package/assets/{.shared/data → data}/patterns.csv +0 -0
- /package/assets/{.shared/data → data}/products.csv +0 -0
- /package/assets/{.shared/data → data}/prompts.csv +0 -0
- /package/assets/{.shared/data → data}/styles.csv +0 -0
- /package/assets/{.shared/data → data}/typography.csv +0 -0
- /package/assets/{.shared/data → data}/ux-guidelines.csv +0 -0
- /package/assets/{.shared/data → data}/widget.csv +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { getLatestRelease } from '../utils/github.js';
|
|
4
|
+
import { logger } from '../utils/logger.js';
|
|
5
|
+
import { initCommand } from './init.js';
|
|
6
|
+
export async function updateCommand(options) {
|
|
7
|
+
logger.title('🔄 Flutter Pro Max Updater');
|
|
8
|
+
const spinner = ora('Checking for updates...').start();
|
|
9
|
+
try {
|
|
10
|
+
const release = await getLatestRelease();
|
|
11
|
+
spinner.succeed(`Latest version: ${chalk.cyan(release.tag_name)}`);
|
|
12
|
+
console.log();
|
|
13
|
+
logger.info('Running update (same as init with latest version)...');
|
|
14
|
+
console.log();
|
|
15
|
+
await initCommand({
|
|
16
|
+
ai: options.ai,
|
|
17
|
+
force: true,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
spinner.fail('Update check failed');
|
|
22
|
+
if (error instanceof Error) {
|
|
23
|
+
logger.error(error.message);
|
|
24
|
+
}
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function versionsCommand(): Promise<void>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { fetchReleases } from '../utils/github.js';
|
|
4
|
+
import { logger } from '../utils/logger.js';
|
|
5
|
+
export async function versionsCommand() {
|
|
6
|
+
const spinner = ora('Fetching available versions...').start();
|
|
7
|
+
try {
|
|
8
|
+
const releases = await fetchReleases();
|
|
9
|
+
if (releases.length === 0) {
|
|
10
|
+
spinner.warn('No releases found');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
spinner.succeed(`Found ${releases.length} version(s)\n`);
|
|
14
|
+
console.log(chalk.bold('Available versions:\n'));
|
|
15
|
+
releases.forEach((release, index) => {
|
|
16
|
+
const isLatest = index === 0;
|
|
17
|
+
const tag = release.tag_name;
|
|
18
|
+
const date = new Date(release.published_at).toLocaleDateString();
|
|
19
|
+
if (isLatest) {
|
|
20
|
+
console.log(` ${chalk.green('*')} ${chalk.bold(tag)} ${chalk.dim(`(${date})`)} ${chalk.green('[latest]')}`);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.log(` ${tag} ${chalk.dim(`(${date})`)}`);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
console.log();
|
|
27
|
+
logger.dim('Use: flutter-pro-max init --version <tag> to install a specific version');
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
spinner.fail('Failed to fetch versions');
|
|
31
|
+
if (error instanceof Error) {
|
|
32
|
+
logger.error(error.message);
|
|
33
|
+
}
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
3
6
|
import { initCommand } from './commands/init.js';
|
|
7
|
+
import { versionsCommand } from './commands/versions.js';
|
|
8
|
+
import { updateCommand } from './commands/update.js';
|
|
4
9
|
import { AI_TYPES } from './types/index.js';
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
|
|
5
13
|
const program = new Command();
|
|
6
14
|
program
|
|
7
15
|
.name('flutter-pro-max')
|
|
8
16
|
.description('CLI to install Flutter Pro Max skill for AI coding assistants')
|
|
9
|
-
.version(
|
|
17
|
+
.version(pkg.version);
|
|
10
18
|
program
|
|
11
19
|
.command('init')
|
|
12
20
|
.description('Install Flutter Pro Max skill to current project')
|
|
@@ -23,6 +31,24 @@ program
|
|
|
23
31
|
force: options.force,
|
|
24
32
|
});
|
|
25
33
|
});
|
|
34
|
+
program
|
|
35
|
+
.command('versions')
|
|
36
|
+
.description('List available versions')
|
|
37
|
+
.action(versionsCommand);
|
|
38
|
+
program
|
|
39
|
+
.command('update')
|
|
40
|
+
.description('Update Flutter Pro Max to latest version')
|
|
41
|
+
.option('-a, --ai <type>', `AI assistant type (${AI_TYPES.join(', ')})`)
|
|
42
|
+
.action(async (options) => {
|
|
43
|
+
if (options.ai && !AI_TYPES.includes(options.ai)) {
|
|
44
|
+
console.error(`Invalid AI type: ${options.ai}`);
|
|
45
|
+
console.error(`Valid types: ${AI_TYPES.join(', ')}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
await updateCommand({
|
|
49
|
+
ai: options.ai,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
26
52
|
// Default command (when run without subcommand)
|
|
27
53
|
program
|
|
28
54
|
.action(async () => {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export type AIType = 'claude' | 'cursor' | 'windsurf' | 'antigravity' | 'copilot' | 'kiro' | 'roocode' | 'codex' | 'qoder' | 'gemini' | 'codebuddy' | 'trae' | 'all';
|
|
1
|
+
export type AIType = 'claude' | 'cursor' | 'windsurf' | 'antigravity' | 'copilot' | 'kiro' | 'roocode' | 'codex' | 'qoder' | 'gemini' | 'codebuddy' | 'trae' | 'opencode' | 'continue' | 'all';
|
|
2
|
+
export type InstallType = 'full' | 'reference';
|
|
2
3
|
export interface Release {
|
|
3
4
|
tag_name: string;
|
|
4
5
|
name: string;
|
|
@@ -16,5 +17,23 @@ export interface InstallConfig {
|
|
|
16
17
|
version?: string;
|
|
17
18
|
force?: boolean;
|
|
18
19
|
}
|
|
20
|
+
export interface PlatformConfig {
|
|
21
|
+
platform: string;
|
|
22
|
+
displayName: string;
|
|
23
|
+
installType: InstallType;
|
|
24
|
+
folderStructure: {
|
|
25
|
+
root: string;
|
|
26
|
+
skillPath: string;
|
|
27
|
+
filename: string;
|
|
28
|
+
};
|
|
29
|
+
scriptPath: string;
|
|
30
|
+
frontmatter: Record<string, string> | null;
|
|
31
|
+
sections: {
|
|
32
|
+
quickReference: boolean;
|
|
33
|
+
};
|
|
34
|
+
title: string;
|
|
35
|
+
description: string;
|
|
36
|
+
skillOrWorkflow: string;
|
|
37
|
+
}
|
|
19
38
|
export declare const AI_TYPES: AIType[];
|
|
20
39
|
export declare const AI_FOLDERS: Record<Exclude<AIType, 'all'>, string[]>;
|
package/dist/types/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export const AI_TYPES = ['claude', 'cursor', 'windsurf', 'antigravity', 'copilot', 'roocode', 'kiro', 'codex', 'qoder', 'gemini', 'codebuddy', 'trae', 'all'];
|
|
1
|
+
export const AI_TYPES = ['claude', 'cursor', 'windsurf', 'antigravity', 'copilot', 'roocode', 'kiro', 'codex', 'qoder', 'gemini', 'codebuddy', 'trae', 'opencode', 'continue', 'all'];
|
|
2
|
+
// Legacy folder mapping for backward compatibility with ZIP-based installs
|
|
2
3
|
export const AI_FOLDERS = {
|
|
3
4
|
claude: ['.claude', '.shared'],
|
|
4
5
|
cursor: ['.cursor', '.shared'],
|
|
@@ -12,4 +13,6 @@ export const AI_FOLDERS = {
|
|
|
12
13
|
gemini: ['.gemini', '.shared'],
|
|
13
14
|
codebuddy: ['.codebuddy', '.shared'],
|
|
14
15
|
trae: ['.trae', '.shared'],
|
|
16
|
+
opencode: ['.opencode', '.shared'],
|
|
17
|
+
continue: ['.continue', '.shared'],
|
|
15
18
|
};
|
package/dist/utils/detect.js
CHANGED
|
@@ -38,6 +38,12 @@ export function detectAIType(cwd = process.cwd()) {
|
|
|
38
38
|
if (existsSync(join(cwd, '.trae'))) {
|
|
39
39
|
detected.push('trae');
|
|
40
40
|
}
|
|
41
|
+
if (existsSync(join(cwd, '.opencode'))) {
|
|
42
|
+
detected.push('opencode');
|
|
43
|
+
}
|
|
44
|
+
if (existsSync(join(cwd, '.continue'))) {
|
|
45
|
+
detected.push('continue');
|
|
46
|
+
}
|
|
41
47
|
// Suggest based on what's detected
|
|
42
48
|
let suggested = null;
|
|
43
49
|
if (detected.length === 1) {
|
|
@@ -57,7 +63,7 @@ export function getAITypeDescription(aiType) {
|
|
|
57
63
|
case 'windsurf':
|
|
58
64
|
return 'Windsurf (.windsurf/workflows/)';
|
|
59
65
|
case 'antigravity':
|
|
60
|
-
return 'Antigravity (.agent/
|
|
66
|
+
return 'Antigravity / Generic Agent (.agent/skills/)';
|
|
61
67
|
case 'copilot':
|
|
62
68
|
return 'GitHub Copilot (.github/prompts/)';
|
|
63
69
|
case 'kiro':
|
|
@@ -74,6 +80,10 @@ export function getAITypeDescription(aiType) {
|
|
|
74
80
|
return 'CodeBuddy (.codebuddy/commands/)';
|
|
75
81
|
case 'trae':
|
|
76
82
|
return 'Trae (.trae/skills/)';
|
|
83
|
+
case 'opencode':
|
|
84
|
+
return 'OpenCode (.opencode/skills/)';
|
|
85
|
+
case 'continue':
|
|
86
|
+
return 'Continue (.continue/skills/)';
|
|
77
87
|
case 'all':
|
|
78
88
|
return 'All AI assistants';
|
|
79
89
|
}
|
package/dist/utils/extract.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { AIType } from '../types/index.js';
|
|
2
2
|
export declare function extractZip(zipPath: string, destDir: string): Promise<void>;
|
|
3
|
+
export declare function createTempDir(): Promise<string>;
|
|
4
|
+
export declare function installFromZip(zipPath: string, targetDir: string, aiType: AIType): Promise<{
|
|
5
|
+
copiedFolders: string[];
|
|
6
|
+
tempDir: string;
|
|
7
|
+
}>;
|
|
3
8
|
export declare function copyFolders(sourceDir: string, targetDir: string, aiType: AIType): Promise<string[]>;
|
|
4
9
|
export declare function cleanup(tempDir: string): Promise<void>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Release } from '../types/index.js';
|
|
2
|
+
export declare class GitHubRateLimitError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class GitHubDownloadError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
export declare function fetchReleases(): Promise<Release[]>;
|
|
9
|
+
export declare function getLatestRelease(): Promise<Release>;
|
|
10
|
+
export declare function downloadRelease(url: string, dest: string): Promise<void>;
|
|
11
|
+
export declare function getAssetUrl(release: Release): string | null;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
const REPO_OWNER = 'nextlevelbuilder';
|
|
3
|
+
const REPO_NAME = 'flutter-pro-max-skill';
|
|
4
|
+
const API_BASE = 'https://api.github.com';
|
|
5
|
+
export class GitHubRateLimitError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'GitHubRateLimitError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class GitHubDownloadError extends Error {
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'GitHubDownloadError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function checkRateLimit(response) {
|
|
18
|
+
const remaining = response.headers.get('x-ratelimit-remaining');
|
|
19
|
+
if (response.status === 403 && remaining === '0') {
|
|
20
|
+
const resetTime = response.headers.get('x-ratelimit-reset');
|
|
21
|
+
const resetDate = resetTime ? new Date(parseInt(resetTime) * 1000).toLocaleTimeString() : 'unknown';
|
|
22
|
+
throw new GitHubRateLimitError(`GitHub API rate limit exceeded. Resets at ${resetDate}`);
|
|
23
|
+
}
|
|
24
|
+
if (response.status === 429) {
|
|
25
|
+
throw new GitHubRateLimitError('GitHub API rate limit exceeded (429 Too Many Requests)');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export async function fetchReleases() {
|
|
29
|
+
const url = `${API_BASE}/repos/${REPO_OWNER}/${REPO_NAME}/releases`;
|
|
30
|
+
const response = await fetch(url, {
|
|
31
|
+
headers: {
|
|
32
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
33
|
+
'User-Agent': 'flutter-pro-max-cli',
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
checkRateLimit(response);
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
throw new GitHubDownloadError(`Failed to fetch releases: ${response.status} ${response.statusText}`);
|
|
39
|
+
}
|
|
40
|
+
return response.json();
|
|
41
|
+
}
|
|
42
|
+
export async function getLatestRelease() {
|
|
43
|
+
const url = `${API_BASE}/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`;
|
|
44
|
+
const response = await fetch(url, {
|
|
45
|
+
headers: {
|
|
46
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
47
|
+
'User-Agent': 'flutter-pro-max-cli',
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
checkRateLimit(response);
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
throw new GitHubDownloadError(`Failed to fetch latest release: ${response.status} ${response.statusText}`);
|
|
53
|
+
}
|
|
54
|
+
return response.json();
|
|
55
|
+
}
|
|
56
|
+
export async function downloadRelease(url, dest) {
|
|
57
|
+
const response = await fetch(url, {
|
|
58
|
+
headers: {
|
|
59
|
+
'User-Agent': 'flutter-pro-max-cli',
|
|
60
|
+
'Accept': 'application/octet-stream',
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
checkRateLimit(response);
|
|
64
|
+
if (!response.ok) {
|
|
65
|
+
throw new GitHubDownloadError(`Failed to download: ${response.status} ${response.statusText}`);
|
|
66
|
+
}
|
|
67
|
+
const buffer = await response.arrayBuffer();
|
|
68
|
+
await writeFile(dest, Buffer.from(buffer));
|
|
69
|
+
}
|
|
70
|
+
export function getAssetUrl(release) {
|
|
71
|
+
// First try to find an uploaded ZIP asset
|
|
72
|
+
const asset = release.assets.find(a => a.name.endsWith('.zip'));
|
|
73
|
+
if (asset?.browser_download_url) {
|
|
74
|
+
return asset.browser_download_url;
|
|
75
|
+
}
|
|
76
|
+
// Fall back to GitHub's auto-generated archive
|
|
77
|
+
if (release.tag_name) {
|
|
78
|
+
return `https://github.com/${REPO_OWNER}/${REPO_NAME}/archive/refs/tags/${release.tag_name}.zip`;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { PlatformConfig } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Load platform configuration from JSON file
|
|
4
|
+
*/
|
|
5
|
+
export declare function loadPlatformConfig(aiType: string): Promise<PlatformConfig>;
|
|
6
|
+
/**
|
|
7
|
+
* Load all available platform configs
|
|
8
|
+
*/
|
|
9
|
+
export declare function loadAllPlatformConfigs(): Promise<Map<string, PlatformConfig>>;
|
|
10
|
+
/**
|
|
11
|
+
* Render skill file content from template
|
|
12
|
+
*/
|
|
13
|
+
export declare function renderSkillFile(config: PlatformConfig): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Generate platform files for a specific AI type
|
|
16
|
+
*/
|
|
17
|
+
export declare function generatePlatformFiles(targetDir: string, aiType: string): Promise<string[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Generate files for all AI types
|
|
20
|
+
*/
|
|
21
|
+
export declare function generateAllPlatformFiles(targetDir: string): Promise<string[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Get list of supported AI types
|
|
24
|
+
*/
|
|
25
|
+
export declare function getSupportedAITypes(): string[];
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { readFile, mkdir, writeFile, cp, access } from 'node:fs/promises';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
// After build: dist/utils/template.js -> ../../assets = cli/assets ✓
|
|
6
|
+
const ASSETS_DIR = join(__dirname, '..', '..', 'assets');
|
|
7
|
+
// Map AIType to platform config file name
|
|
8
|
+
const AI_TO_PLATFORM = {
|
|
9
|
+
claude: 'claude',
|
|
10
|
+
cursor: 'cursor',
|
|
11
|
+
windsurf: 'windsurf',
|
|
12
|
+
antigravity: 'agent',
|
|
13
|
+
copilot: 'copilot',
|
|
14
|
+
kiro: 'kiro',
|
|
15
|
+
roocode: 'roocode',
|
|
16
|
+
codex: 'codex',
|
|
17
|
+
qoder: 'qoder',
|
|
18
|
+
gemini: 'gemini',
|
|
19
|
+
trae: 'trae',
|
|
20
|
+
codebuddy: 'codebuddy',
|
|
21
|
+
opencode: 'opencode',
|
|
22
|
+
continue: 'continue',
|
|
23
|
+
};
|
|
24
|
+
async function exists(path) {
|
|
25
|
+
try {
|
|
26
|
+
await access(path);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Load platform configuration from JSON file
|
|
35
|
+
*/
|
|
36
|
+
export async function loadPlatformConfig(aiType) {
|
|
37
|
+
const platformName = AI_TO_PLATFORM[aiType];
|
|
38
|
+
if (!platformName) {
|
|
39
|
+
throw new Error(`Unknown AI type: ${aiType}`);
|
|
40
|
+
}
|
|
41
|
+
const configPath = join(ASSETS_DIR, 'templates', 'platforms', `${platformName}.json`);
|
|
42
|
+
const content = await readFile(configPath, 'utf-8');
|
|
43
|
+
return JSON.parse(content);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Load all available platform configs
|
|
47
|
+
*/
|
|
48
|
+
export async function loadAllPlatformConfigs() {
|
|
49
|
+
const configs = new Map();
|
|
50
|
+
for (const [aiType, platformName] of Object.entries(AI_TO_PLATFORM)) {
|
|
51
|
+
try {
|
|
52
|
+
const config = await loadPlatformConfig(aiType);
|
|
53
|
+
configs.set(aiType, config);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Skip if config doesn't exist
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return configs;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Load a template file
|
|
63
|
+
*/
|
|
64
|
+
async function loadTemplate(templateName) {
|
|
65
|
+
const templatePath = join(ASSETS_DIR, 'templates', templateName);
|
|
66
|
+
return readFile(templatePath, 'utf-8');
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Render frontmatter section
|
|
70
|
+
*/
|
|
71
|
+
function renderFrontmatter(frontmatter) {
|
|
72
|
+
if (!frontmatter)
|
|
73
|
+
return '';
|
|
74
|
+
const lines = ['---'];
|
|
75
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
76
|
+
// Quote values that contain special characters
|
|
77
|
+
if (value.includes(':') || value.includes('"') || value.includes('\n')) {
|
|
78
|
+
lines.push(`${key}: "${value.replace(/"/g, '\\"')}"`);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
lines.push(`${key}: ${value}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
lines.push('---', '');
|
|
85
|
+
return lines.join('\n');
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Render skill file content from template
|
|
89
|
+
*/
|
|
90
|
+
export async function renderSkillFile(config) {
|
|
91
|
+
// Load base template
|
|
92
|
+
let content = await loadTemplate('base/skill-content.md');
|
|
93
|
+
// Load quick reference if needed
|
|
94
|
+
let quickReferenceContent = '';
|
|
95
|
+
if (config.sections.quickReference) {
|
|
96
|
+
quickReferenceContent = await loadTemplate('base/quick-reference.md');
|
|
97
|
+
// Replace script path in quick reference
|
|
98
|
+
quickReferenceContent = quickReferenceContent.replace(/\{\{SCRIPT_PATH\}\}/g, config.scriptPath);
|
|
99
|
+
}
|
|
100
|
+
// Build the final content
|
|
101
|
+
const frontmatter = renderFrontmatter(config.frontmatter);
|
|
102
|
+
// Replace placeholders
|
|
103
|
+
const quickRefWithNewline = quickReferenceContent ? '\n' + quickReferenceContent : '';
|
|
104
|
+
content = content
|
|
105
|
+
.replace(/\{\{TITLE\}\}/g, config.title)
|
|
106
|
+
.replace(/\{\{DESCRIPTION\}\}/g, config.description)
|
|
107
|
+
.replace(/\{\{SCRIPT_PATH\}\}/g, config.scriptPath)
|
|
108
|
+
.replace(/\{\{SKILL_OR_WORKFLOW\}\}/g, config.skillOrWorkflow)
|
|
109
|
+
.replace(/\{\{QUICK_REFERENCE\}\}/g, quickRefWithNewline);
|
|
110
|
+
return frontmatter + content;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Copy data and scripts to target directory
|
|
114
|
+
*/
|
|
115
|
+
async function copyDataAndScripts(targetSkillDir) {
|
|
116
|
+
const dataSource = join(ASSETS_DIR, 'data');
|
|
117
|
+
const scriptsSource = join(ASSETS_DIR, 'scripts');
|
|
118
|
+
const dataTarget = join(targetSkillDir, 'data');
|
|
119
|
+
const scriptsTarget = join(targetSkillDir, 'scripts');
|
|
120
|
+
// Copy data
|
|
121
|
+
if (await exists(dataSource)) {
|
|
122
|
+
await mkdir(dataTarget, { recursive: true });
|
|
123
|
+
await cp(dataSource, dataTarget, { recursive: true });
|
|
124
|
+
}
|
|
125
|
+
// Copy scripts
|
|
126
|
+
if (await exists(scriptsSource)) {
|
|
127
|
+
await mkdir(scriptsTarget, { recursive: true });
|
|
128
|
+
await cp(scriptsSource, scriptsTarget, { recursive: true });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Ensure .shared folder exists with data and scripts
|
|
133
|
+
*/
|
|
134
|
+
async function ensureSharedExists(targetDir) {
|
|
135
|
+
const sharedDir = join(targetDir, '.shared', 'flutter-pro-max');
|
|
136
|
+
// Check if already exists
|
|
137
|
+
if (await exists(sharedDir)) {
|
|
138
|
+
return false; // Already exists, didn't create
|
|
139
|
+
}
|
|
140
|
+
await mkdir(sharedDir, { recursive: true });
|
|
141
|
+
await copyDataAndScripts(sharedDir);
|
|
142
|
+
return true; // Created new
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Generate platform files for a specific AI type
|
|
146
|
+
*/
|
|
147
|
+
export async function generatePlatformFiles(targetDir, aiType) {
|
|
148
|
+
const config = await loadPlatformConfig(aiType);
|
|
149
|
+
const createdFolders = [];
|
|
150
|
+
// Determine full skill directory path
|
|
151
|
+
const skillDir = join(targetDir, config.folderStructure.root, config.folderStructure.skillPath);
|
|
152
|
+
// Create directory structure
|
|
153
|
+
await mkdir(skillDir, { recursive: true });
|
|
154
|
+
// Render and write skill file
|
|
155
|
+
const skillContent = await renderSkillFile(config);
|
|
156
|
+
const skillFilePath = join(skillDir, config.folderStructure.filename);
|
|
157
|
+
await writeFile(skillFilePath, skillContent, 'utf-8');
|
|
158
|
+
createdFolders.push(config.folderStructure.root);
|
|
159
|
+
// Handle data/scripts based on install type
|
|
160
|
+
if (config.installType === 'full') {
|
|
161
|
+
// Full install: copy data and scripts into the skill directory
|
|
162
|
+
await copyDataAndScripts(skillDir);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// Reference install: ensure .shared exists
|
|
166
|
+
const createdShared = await ensureSharedExists(targetDir);
|
|
167
|
+
if (createdShared) {
|
|
168
|
+
createdFolders.push('.shared');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return createdFolders;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Generate files for all AI types
|
|
175
|
+
*/
|
|
176
|
+
export async function generateAllPlatformFiles(targetDir) {
|
|
177
|
+
const allFolders = new Set();
|
|
178
|
+
for (const aiType of Object.keys(AI_TO_PLATFORM)) {
|
|
179
|
+
try {
|
|
180
|
+
const folders = await generatePlatformFiles(targetDir, aiType);
|
|
181
|
+
folders.forEach(f => allFolders.add(f));
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// Skip if generation fails for a platform
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return Array.from(allFolders);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get list of supported AI types
|
|
191
|
+
*/
|
|
192
|
+
export function getSupportedAITypes() {
|
|
193
|
+
return Object.keys(AI_TO_PLATFORM);
|
|
194
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flutter-pro-max-cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "CLI to install Flutter Pro Max skill for AI coding assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,9 +12,8 @@
|
|
|
12
12
|
"LICENSE"
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
|
-
"
|
|
16
|
-
"build": "
|
|
17
|
-
"build:bun": "npm run sync:assets && bun build src/index.ts --outdir dist --target node",
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"build:bun": "bun build src/index.ts --outdir dist --target node",
|
|
18
17
|
"dev": "npx ts-node --esm src/index.ts",
|
|
19
18
|
"prepublishOnly": "npm run build"
|
|
20
19
|
},
|
|
@@ -32,6 +31,11 @@
|
|
|
32
31
|
"roocode",
|
|
33
32
|
"codex",
|
|
34
33
|
"qoder",
|
|
34
|
+
"gemini",
|
|
35
|
+
"opencode",
|
|
36
|
+
"continue",
|
|
37
|
+
"codebuddy",
|
|
38
|
+
"trae",
|
|
35
39
|
"ai",
|
|
36
40
|
"skill"
|
|
37
41
|
],
|