agent-flutter 0.1.1 → 0.1.3

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.
Files changed (3) hide show
  1. package/README.md +5 -1
  2. package/package.json +1 -1
  3. package/src/cli.js +160 -22
package/README.md CHANGED
@@ -14,12 +14,15 @@ By default `init` installs adapters for all supported IDEs:
14
14
  - Cursor
15
15
  - Windsurf
16
16
  - Cline
17
+ - GitHub (Copilot)
17
18
 
18
19
  ## Commands
19
20
 
20
21
  ```bash
21
22
  npx agent-flutter@latest init --ide all --cwd /path/to/project --force
22
23
  npx agent-flutter@latest init --ide trae,codex
24
+ npx agent-flutter@latest sync
25
+ npx agent-flutter@latest sync --ide trae,codex,github
23
26
  npx agent-flutter@latest list --cwd /path/to/project
24
27
  ```
25
28
 
@@ -49,9 +52,10 @@ npm run release:major
49
52
 
50
53
  ## Installed files
51
54
 
52
- - Shared pack: `.agent-flutter/`
55
+ - Shared pack (for Trae/Codex/Cursor/Windsurf/Cline): `.agent-flutter/`
53
56
  - Trae: `.trae/`
54
57
  - Codex: `AGENTS.md`
55
58
  - Cursor: `.cursor/rules/agent-flutter.mdc`
56
59
  - Windsurf: `.windsurf/rules/agent-flutter.md`
57
60
  - Cline: `.clinerules/agent-flutter.md`
61
+ - GitHub: `.github/skills/`, `.github/rules/`, `.github/copilot-instructions.md`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-flutter",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Portable Flutter skill/rule pack initializer for multiple AI IDEs.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -2,17 +2,19 @@ import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { fileURLToPath, pathToFileURL } from 'node:url';
4
4
 
5
- const SUPPORTED_IDES = ['trae', 'codex', 'cursor', 'windsurf', 'cline'];
5
+ const SUPPORTED_IDES = ['trae', 'codex', 'cursor', 'windsurf', 'cline', 'github'];
6
6
 
7
7
  const USAGE = `
8
8
  agent-flutter
9
9
 
10
10
  Usage:
11
- npx agent-flutter@latest init [--ide all|trae,codex,cursor,windsurf,cline] [--cwd <project_dir>] [--force]
11
+ npx agent-flutter@latest init [--ide all|trae,codex,cursor,windsurf,cline,github] [--cwd <project_dir>] [--force]
12
+ npx agent-flutter@latest sync [--ide all|trae,codex,cursor,windsurf,cline,github] [--cwd <project_dir>]
12
13
  npx agent-flutter@latest list [--cwd <project_dir>]
13
14
 
14
15
  Commands:
15
16
  init Install shared Flutter skills/rules and IDE adapters.
17
+ sync Update installed shared pack and adapters from latest template.
16
18
  list Print available skills/rules from the shared pack.
17
19
  `;
18
20
 
@@ -29,6 +31,9 @@ export async function runCli(argv) {
29
31
  case 'init':
30
32
  await runInit(options);
31
33
  return;
34
+ case 'sync':
35
+ await runSync(options);
36
+ return;
32
37
  case 'list':
33
38
  await runList(options);
34
39
  return;
@@ -82,17 +87,76 @@ async function runInit(options) {
82
87
  const ideTargets = resolveIdeTargets(options.get('ide', 'all'));
83
88
  const force = options.hasFlag('force');
84
89
 
85
- const sharedTarget = path.join(projectRoot, '.agent-flutter');
86
- if ((await exists(sharedTarget)) && !force) {
87
- console.log(`Using existing shared pack: ${sharedTarget}`);
90
+ await applyPack({
91
+ templateRoot,
92
+ projectRoot,
93
+ ideTargets,
94
+ force,
95
+ mode: 'install',
96
+ });
97
+
98
+ console.log('');
99
+ console.log('Done.');
100
+ console.log('Use --force to overwrite existing adapters.');
101
+ }
102
+
103
+ async function runSync(options) {
104
+ const packageRoot = getPackageRoot();
105
+ const templateRoot = path.join(packageRoot, 'templates', 'shared');
106
+ await assertDirExists(templateRoot, `Shared template not found: ${templateRoot}`);
107
+
108
+ const projectRoot = path.resolve(options.get('cwd', process.cwd()));
109
+ await assertDirExists(projectRoot, `Project directory not found: ${projectRoot}`);
110
+
111
+ let ideTargets;
112
+ const ideOption = options.get('ide', null);
113
+ if (ideOption) {
114
+ ideTargets = resolveIdeTargets(ideOption);
88
115
  } else {
89
- await copyTemplateDirectory({
90
- sourceDir: templateRoot,
91
- destinationDir: sharedTarget,
92
- projectRoot,
93
- force: true,
94
- });
95
- console.log(`Installed shared pack: ${sharedTarget}`);
116
+ ideTargets = await detectInstalledIdeTargets(projectRoot);
117
+ if (ideTargets.size) {
118
+ console.log(`Detected adapters: ${[...ideTargets].join(', ')}`);
119
+ } else {
120
+ ideTargets = new Set(SUPPORTED_IDES);
121
+ console.log('No existing adapters detected. Syncing all adapters.');
122
+ }
123
+ }
124
+
125
+ await applyPack({
126
+ templateRoot,
127
+ projectRoot,
128
+ ideTargets,
129
+ force: true,
130
+ mode: 'sync',
131
+ });
132
+
133
+ console.log('');
134
+ console.log('Sync completed.');
135
+ }
136
+
137
+ async function applyPack({
138
+ templateRoot,
139
+ projectRoot,
140
+ ideTargets,
141
+ force,
142
+ mode,
143
+ }) {
144
+ const verb = mode === 'sync' ? 'Synced' : 'Installed';
145
+ const sharedPackIdes = new Set(['trae', 'codex', 'cursor', 'windsurf', 'cline']);
146
+ const requiresSharedPack = [...ideTargets].some((ide) => sharedPackIdes.has(ide));
147
+ const sharedTarget = path.join(projectRoot, '.agent-flutter');
148
+ if (requiresSharedPack) {
149
+ if ((await exists(sharedTarget)) && !force) {
150
+ console.log(`Using existing shared pack: ${sharedTarget}`);
151
+ } else {
152
+ await copyTemplateDirectory({
153
+ sourceDir: templateRoot,
154
+ destinationDir: sharedTarget,
155
+ projectRoot,
156
+ force: true,
157
+ });
158
+ console.log(`${verb} shared pack: ${sharedTarget}`);
159
+ }
96
160
  }
97
161
 
98
162
  if (ideTargets.has('trae')) {
@@ -106,12 +170,15 @@ async function runInit(options) {
106
170
  projectRoot,
107
171
  force: true,
108
172
  });
109
- console.log(`Installed Trae adapter: ${traeTarget}`);
173
+ console.log(`${verb} Trae adapter: ${traeTarget}`);
110
174
  }
111
175
  }
112
176
 
113
- const skills = await loadSkillMetadata(path.join(sharedTarget, 'skills'));
114
- const rules = await loadRuleMetadata(path.join(sharedTarget, 'rules'));
177
+ const metadataSource = requiresSharedPack
178
+ ? sharedTarget
179
+ : templateRoot;
180
+ const skills = await loadSkillMetadata(path.join(metadataSource, 'skills'));
181
+ const rules = await loadRuleMetadata(path.join(metadataSource, 'rules'));
115
182
 
116
183
  if (ideTargets.has('codex')) {
117
184
  const agentsPath = path.join(projectRoot, 'AGENTS.md');
@@ -127,7 +194,7 @@ async function runInit(options) {
127
194
  );
128
195
  console.log(
129
196
  written
130
- ? `Installed Codex adapter: ${agentsPath}`
197
+ ? `${verb} Codex adapter: ${agentsPath}`
131
198
  : `Skipped Codex adapter (exists): ${agentsPath}`,
132
199
  );
133
200
  }
@@ -141,7 +208,7 @@ async function runInit(options) {
141
208
  );
142
209
  console.log(
143
210
  written
144
- ? `Installed Cursor adapter: ${cursorPath}`
211
+ ? `${verb} Cursor adapter: ${cursorPath}`
145
212
  : `Skipped Cursor adapter (exists): ${cursorPath}`,
146
213
  );
147
214
  }
@@ -155,7 +222,7 @@ async function runInit(options) {
155
222
  );
156
223
  console.log(
157
224
  written
158
- ? `Installed Windsurf adapter: ${windsurfPath}`
225
+ ? `${verb} Windsurf adapter: ${windsurfPath}`
159
226
  : `Skipped Windsurf adapter (exists): ${windsurfPath}`,
160
227
  );
161
228
  }
@@ -169,14 +236,72 @@ async function runInit(options) {
169
236
  );
170
237
  console.log(
171
238
  written
172
- ? `Installed Cline adapter: ${clinePath}`
239
+ ? `${verb} Cline adapter: ${clinePath}`
173
240
  : `Skipped Cline adapter (exists): ${clinePath}`,
174
241
  );
175
242
  }
176
243
 
177
- console.log('');
178
- console.log('Done.');
179
- console.log('Use --force to overwrite existing adapters.');
244
+ if (ideTargets.has('github')) {
245
+ const githubSkillsPath = path.join(projectRoot, '.github', 'skills');
246
+ if ((await exists(githubSkillsPath)) && !force) {
247
+ console.log(`Skipped GitHub skills (exists): ${githubSkillsPath}`);
248
+ } else {
249
+ await copyTemplateDirectory({
250
+ sourceDir: path.join(templateRoot, 'skills'),
251
+ destinationDir: githubSkillsPath,
252
+ projectRoot,
253
+ force: true,
254
+ });
255
+ console.log(`${verb} GitHub skills: ${githubSkillsPath}`);
256
+ }
257
+
258
+ const githubRulesPath = path.join(projectRoot, '.github', 'rules');
259
+ if ((await exists(githubRulesPath)) && !force) {
260
+ console.log(`Skipped GitHub rules (exists): ${githubRulesPath}`);
261
+ } else {
262
+ await copyTemplateDirectory({
263
+ sourceDir: path.join(templateRoot, 'rules'),
264
+ destinationDir: githubRulesPath,
265
+ projectRoot,
266
+ force: true,
267
+ });
268
+ console.log(`${verb} GitHub rules: ${githubRulesPath}`);
269
+ }
270
+
271
+ const githubPath = path.join(projectRoot, '.github', 'copilot-instructions.md');
272
+ const written = await writeTextFile(
273
+ githubPath,
274
+ buildGithubCopilotInstructions(),
275
+ { force },
276
+ );
277
+ console.log(
278
+ written
279
+ ? `${verb} GitHub adapter: ${githubPath}`
280
+ : `Skipped GitHub adapter (exists): ${githubPath}`,
281
+ );
282
+ }
283
+ }
284
+
285
+ async function detectInstalledIdeTargets(projectRoot) {
286
+ const detected = new Set();
287
+ if (await exists(path.join(projectRoot, '.trae'))) detected.add('trae');
288
+ if (await exists(path.join(projectRoot, 'AGENTS.md'))) detected.add('codex');
289
+ if (await exists(path.join(projectRoot, '.cursor', 'rules', 'agent-flutter.mdc'))) {
290
+ detected.add('cursor');
291
+ }
292
+ if (await exists(path.join(projectRoot, '.windsurf', 'rules', 'agent-flutter.md'))) {
293
+ detected.add('windsurf');
294
+ }
295
+ if (await exists(path.join(projectRoot, '.clinerules', 'agent-flutter.md'))) {
296
+ detected.add('cline');
297
+ }
298
+ if (
299
+ (await exists(path.join(projectRoot, '.github', 'copilot-instructions.md')))
300
+ || (await exists(path.join(projectRoot, '.github', 'skills')))
301
+ ) {
302
+ detected.add('github');
303
+ }
304
+ return detected;
180
305
  }
181
306
 
182
307
  async function runList(options) {
@@ -467,6 +592,19 @@ Execution checklist:
467
592
  `;
468
593
  }
469
594
 
595
+ function buildGithubCopilotInstructions() {
596
+ return `# Agent Flutter Copilot Instructions
597
+
598
+ This repository uses local instruction packs in \`.github/skills\` and \`.github/rules\`.
599
+
600
+ Follow this order when generating code:
601
+ 1. Read applicable files in \`.github/rules/\`.
602
+ 2. If task matches a skill, read \`.github/skills/<skill>/SKILL.md\`.
603
+ 3. Keep architecture, localization, and UI conventions aligned with the shared pack.
604
+ 4. Update specs/docs when UI/API behavior changes.
605
+ `;
606
+ }
607
+
470
608
  async function exists(filePath) {
471
609
  try {
472
610
  await fs.access(filePath);