@fitlab-ai/agent-infra 0.5.9 → 0.6.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.
Files changed (100) hide show
  1. package/README.md +200 -8
  2. package/README.zh-CN.md +176 -8
  3. package/bin/{cli.js → cli.ts} +23 -19
  4. package/dist/bin/cli.js +116 -0
  5. package/dist/lib/defaults.json +61 -0
  6. package/dist/lib/init.js +238 -0
  7. package/dist/lib/log.js +18 -0
  8. package/dist/lib/merge.js +747 -0
  9. package/dist/lib/paths.js +18 -0
  10. package/dist/lib/prompt.js +85 -0
  11. package/dist/lib/render.js +139 -0
  12. package/dist/lib/sandbox/commands/create.js +1173 -0
  13. package/dist/lib/sandbox/commands/enter.js +98 -0
  14. package/dist/lib/sandbox/commands/ls.js +93 -0
  15. package/dist/lib/sandbox/commands/rebuild.js +101 -0
  16. package/dist/lib/sandbox/commands/refresh.js +85 -0
  17. package/dist/lib/sandbox/commands/rm.js +226 -0
  18. package/dist/lib/sandbox/commands/vm.js +144 -0
  19. package/dist/lib/sandbox/config.js +85 -0
  20. package/dist/lib/sandbox/constants.js +104 -0
  21. package/dist/lib/sandbox/credentials.js +437 -0
  22. package/dist/lib/sandbox/dockerfile.js +76 -0
  23. package/dist/lib/sandbox/dotfiles.js +170 -0
  24. package/dist/lib/sandbox/engine.js +155 -0
  25. package/dist/lib/sandbox/engines/colima.js +64 -0
  26. package/dist/lib/sandbox/engines/docker-desktop.js +27 -0
  27. package/dist/lib/sandbox/engines/index.js +25 -0
  28. package/dist/lib/sandbox/engines/native.js +96 -0
  29. package/dist/lib/sandbox/engines/orbstack.js +63 -0
  30. package/dist/lib/sandbox/engines/selinux.js +48 -0
  31. package/dist/lib/sandbox/engines/wsl2-paths.js +47 -0
  32. package/dist/lib/sandbox/engines/wsl2.js +57 -0
  33. package/dist/lib/sandbox/index.js +70 -0
  34. package/dist/lib/sandbox/runtimes/ai-tools.dockerfile +39 -0
  35. package/dist/lib/sandbox/runtimes/base.dockerfile +178 -0
  36. package/dist/lib/sandbox/runtimes/java17.dockerfile +3 -0
  37. package/dist/lib/sandbox/runtimes/java21.dockerfile +3 -0
  38. package/dist/lib/sandbox/runtimes/node20.dockerfile +3 -0
  39. package/dist/lib/sandbox/runtimes/node22.dockerfile +3 -0
  40. package/dist/lib/sandbox/runtimes/python3.dockerfile +3 -0
  41. package/dist/lib/sandbox/shell.js +148 -0
  42. package/dist/lib/sandbox/task-resolver.js +35 -0
  43. package/dist/lib/sandbox/tools.js +115 -0
  44. package/dist/lib/update.js +186 -0
  45. package/dist/lib/version.js +5 -0
  46. package/dist/package.json +5 -0
  47. package/lib/{init.js → init.ts} +64 -20
  48. package/lib/{log.js → log.ts} +4 -4
  49. package/lib/{merge.js → merge.ts} +129 -63
  50. package/lib/paths.ts +18 -0
  51. package/lib/{prompt.js → prompt.ts} +12 -12
  52. package/lib/{render.js → render.ts} +30 -17
  53. package/lib/sandbox/commands/create.ts +1507 -0
  54. package/lib/sandbox/commands/enter.ts +115 -0
  55. package/lib/sandbox/commands/{ls.js → ls.ts} +41 -10
  56. package/lib/sandbox/commands/rebuild.ts +135 -0
  57. package/lib/sandbox/commands/refresh.ts +128 -0
  58. package/lib/sandbox/commands/{rm.js → rm.ts} +71 -21
  59. package/lib/sandbox/commands/{vm.js → vm.ts} +62 -15
  60. package/lib/sandbox/config.ts +133 -0
  61. package/lib/sandbox/{constants.js → constants.ts} +41 -17
  62. package/lib/sandbox/credentials.ts +634 -0
  63. package/lib/sandbox/{dockerfile.js → dockerfile.ts} +13 -6
  64. package/lib/sandbox/dotfiles.ts +236 -0
  65. package/lib/sandbox/engine.ts +231 -0
  66. package/lib/sandbox/engines/colima.ts +81 -0
  67. package/lib/sandbox/engines/docker-desktop.ts +36 -0
  68. package/lib/sandbox/engines/index.ts +74 -0
  69. package/lib/sandbox/engines/native.ts +131 -0
  70. package/lib/sandbox/engines/orbstack.ts +78 -0
  71. package/lib/sandbox/engines/selinux.ts +66 -0
  72. package/lib/sandbox/engines/wsl2-paths.ts +65 -0
  73. package/lib/sandbox/engines/wsl2.ts +74 -0
  74. package/lib/sandbox/{index.js → index.ts} +17 -8
  75. package/lib/sandbox/runtimes/ai-tools.dockerfile +14 -1
  76. package/lib/sandbox/runtimes/base.dockerfile +116 -1
  77. package/lib/sandbox/shell.ts +186 -0
  78. package/lib/sandbox/{task-resolver.js → task-resolver.ts} +6 -6
  79. package/lib/sandbox/{tools.js → tools.ts} +33 -29
  80. package/lib/{update.js → update.ts} +33 -10
  81. package/package.json +22 -12
  82. package/templates/.agents/rules/create-issue.github.en.md +2 -4
  83. package/templates/.agents/rules/create-issue.github.zh-CN.md +2 -4
  84. package/templates/.agents/rules/issue-pr-commands.github.en.md +29 -0
  85. package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +29 -0
  86. package/templates/.agents/scripts/{platform-adapters/find-existing-task.github.js → find-existing-task.js} +22 -79
  87. package/templates/.agents/scripts/platform-adapters/platform-sync.github.js +26 -41
  88. package/templates/.agents/skills/create-task/SKILL.en.md +1 -1
  89. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -1
  90. package/templates/.agents/skills/import-issue/SKILL.en.md +6 -8
  91. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +6 -8
  92. package/lib/paths.js +0 -9
  93. package/lib/sandbox/commands/create.js +0 -1174
  94. package/lib/sandbox/commands/enter.js +0 -79
  95. package/lib/sandbox/commands/rebuild.js +0 -102
  96. package/lib/sandbox/config.js +0 -84
  97. package/lib/sandbox/engine.js +0 -256
  98. package/lib/sandbox/shell.js +0 -122
  99. package/templates/.agents/scripts/platform-adapters/find-existing-task.js +0 -5
  100. /package/lib/{version.js → version.ts} +0 -0
@@ -0,0 +1,186 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { info, ok, err } from "./log.js";
4
+ import { resolveTemplateDir } from "./paths.js";
5
+ import { renderFile, copySkillDir, KNOWN_PLATFORMS } from "./render.js";
6
+ const defaults = JSON.parse(fs.readFileSync(new URL('./defaults.json', import.meta.url), 'utf8'));
7
+ const CONFIG_DIR = '.agents';
8
+ const CONFIG_PATH = path.join(CONFIG_DIR, '.airc.json');
9
+ function isPathOwnedByOtherPlatform(relativePath, platformType) {
10
+ const top = String(relativePath || '').replace(/\\/g, '/').replace(/^\.\//, '').split('/')[0] ?? '';
11
+ if (!top.startsWith('.'))
12
+ return false;
13
+ const candidate = top.slice(1);
14
+ if (!KNOWN_PLATFORMS.has(candidate))
15
+ return false;
16
+ return candidate !== platformType;
17
+ }
18
+ function syncFileRegistry(config, platformType) {
19
+ config.files ||= {};
20
+ const before = JSON.stringify({
21
+ files: {
22
+ managed: config.files.managed || [],
23
+ merged: config.files.merged || [],
24
+ ejected: config.files.ejected || []
25
+ }
26
+ });
27
+ config.files.managed = config.files.managed || [];
28
+ config.files.merged = config.files.merged || [];
29
+ config.files.ejected = config.files.ejected || [];
30
+ const allExisting = [
31
+ ...config.files.managed,
32
+ ...config.files.merged,
33
+ ...config.files.ejected
34
+ ];
35
+ const added = { managed: [], merged: [] };
36
+ for (const entry of defaults.files.managed) {
37
+ if (isPathOwnedByOtherPlatform(entry, platformType))
38
+ continue;
39
+ if (!allExisting.includes(entry)) {
40
+ config.files.managed.push(entry);
41
+ added.managed.push(entry);
42
+ }
43
+ }
44
+ for (const entry of defaults.files.merged) {
45
+ if (isPathOwnedByOtherPlatform(entry, platformType))
46
+ continue;
47
+ if (!allExisting.includes(entry)) {
48
+ config.files.merged.push(entry);
49
+ added.merged.push(entry);
50
+ }
51
+ }
52
+ const after = JSON.stringify({
53
+ files: {
54
+ managed: config.files.managed,
55
+ merged: config.files.merged,
56
+ ejected: config.files.ejected
57
+ }
58
+ });
59
+ return { added, changed: before !== after };
60
+ }
61
+ async function cmdUpdate() {
62
+ console.log('');
63
+ console.log(' agent-infra update');
64
+ console.log(' ==================================');
65
+ console.log('');
66
+ // check config exists
67
+ if (!fs.existsSync(CONFIG_PATH)) {
68
+ err(`No ${CONFIG_PATH} found in current directory.`);
69
+ err('Run "ai init" first to initialize the project.');
70
+ process.exitCode = 1;
71
+ return;
72
+ }
73
+ // resolve templates
74
+ const templateDir = resolveTemplateDir();
75
+ if (!templateDir) {
76
+ err('Template directory not found.');
77
+ err('Install via npm: npm install -g @fitlab-ai/agent-infra');
78
+ process.exitCode = 1;
79
+ return;
80
+ }
81
+ // read project config
82
+ const config = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
83
+ const { project, org, language } = config;
84
+ const platformType = config.platform?.type || defaults.platform.type;
85
+ const replacements = { project, org };
86
+ info(`Updating seed files for: ${project}`);
87
+ console.log('');
88
+ // select language-specific template filenames
89
+ let claudeSrc, geminiSrc, opencodeSrc;
90
+ if (language === 'zh-CN') {
91
+ claudeSrc = 'update-agent-infra.zh-CN.md';
92
+ geminiSrc = 'update-agent-infra.zh-CN.toml';
93
+ opencodeSrc = 'update-agent-infra.zh-CN.md';
94
+ }
95
+ else {
96
+ claudeSrc = 'update-agent-infra.en.md';
97
+ geminiSrc = 'update-agent-infra.en.toml';
98
+ opencodeSrc = 'update-agent-infra.en.md';
99
+ }
100
+ // update skill
101
+ copySkillDir(path.join(templateDir, '.agents', 'skills', 'update-agent-infra'), path.join('.agents', 'skills', 'update-agent-infra'), replacements, language, platformType);
102
+ ok('Updated .agents/skills/update-agent-infra/');
103
+ try {
104
+ fs.unlinkSync(path.join('.agents', 'skills', 'update-agent-infra', 'scripts', 'sync-templates.cjs'));
105
+ }
106
+ catch {
107
+ // Ignore missing legacy script from pre-ESM installs.
108
+ }
109
+ // update Claude command
110
+ renderFile(path.join(templateDir, '.claude', 'commands', claudeSrc), path.join('.claude', 'commands', 'update-agent-infra.md'), replacements);
111
+ ok('Updated .claude/commands/update-agent-infra.md');
112
+ // update Gemini command
113
+ renderFile(path.join(templateDir, '.gemini', 'commands', '_project_', geminiSrc), path.join('.gemini', 'commands', project, 'update-agent-infra.toml'), replacements);
114
+ ok(`Updated .gemini/commands/${project}/update-agent-infra.toml`);
115
+ // update OpenCode command
116
+ renderFile(path.join(templateDir, '.opencode', 'commands', opencodeSrc), path.join('.opencode', 'commands', 'update-agent-infra.md'), replacements);
117
+ ok('Updated .opencode/commands/update-agent-infra.md');
118
+ // sync file registry
119
+ const { added, changed } = syncFileRegistry(config, platformType);
120
+ const hasNewEntries = added.managed.length > 0 || added.merged.length > 0;
121
+ const platformAdded = !config.platform;
122
+ const sandboxAdded = !config.sandbox;
123
+ const labelsAdded = !config.labels;
124
+ let configChanged = changed;
125
+ if (platformAdded) {
126
+ config.platform = structuredClone(defaults.platform);
127
+ configChanged = true;
128
+ }
129
+ if (sandboxAdded) {
130
+ config.sandbox = structuredClone(defaults.sandbox);
131
+ configChanged = true;
132
+ }
133
+ if (labelsAdded) {
134
+ config.labels = structuredClone(defaults.labels);
135
+ configChanged = true;
136
+ }
137
+ if (configChanged) {
138
+ console.log('');
139
+ if (hasNewEntries) {
140
+ info(`New file entries synced to ${CONFIG_PATH}:`);
141
+ for (const entry of added.managed) {
142
+ ok(` managed: ${entry}`);
143
+ }
144
+ for (const entry of added.merged) {
145
+ ok(` merged: ${entry}`);
146
+ }
147
+ }
148
+ else if (platformAdded || sandboxAdded || labelsAdded) {
149
+ if (platformAdded) {
150
+ info(`Default platform config added to ${CONFIG_PATH}.`);
151
+ }
152
+ if (sandboxAdded) {
153
+ info(`Default sandbox config added to ${CONFIG_PATH}.`);
154
+ }
155
+ if (labelsAdded) {
156
+ info(`Default labels.in config added to ${CONFIG_PATH}.`);
157
+ }
158
+ }
159
+ else {
160
+ info(`File registry changed in ${CONFIG_PATH}.`);
161
+ }
162
+ if (hasNewEntries && sandboxAdded) {
163
+ info(`Default sandbox config added to ${CONFIG_PATH}.`);
164
+ }
165
+ if (hasNewEntries && labelsAdded) {
166
+ info(`Default labels.in config added to ${CONFIG_PATH}.`);
167
+ }
168
+ if (hasNewEntries && platformAdded) {
169
+ info(`Default platform config added to ${CONFIG_PATH}.`);
170
+ }
171
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf8');
172
+ ok(`Updated ${CONFIG_PATH}`);
173
+ }
174
+ // done
175
+ console.log('');
176
+ ok('Seed files updated successfully!');
177
+ console.log('');
178
+ console.log(' Next step: run the full update in your AI TUI:');
179
+ console.log('');
180
+ console.log(' Claude Code / OpenCode: /update-agent-infra');
181
+ console.log(` Gemini CLI: /${project}:update-agent-infra`);
182
+ console.log(' Codex CLI: $update-agent-infra');
183
+ console.log('');
184
+ }
185
+ export { cmdUpdate };
186
+ //# sourceMappingURL=update.js.map
@@ -0,0 +1,5 @@
1
+ import { readFileSync } from 'node:fs';
2
+ const { version } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
3
+ const VERSION = `v${version}`;
4
+ export { VERSION };
5
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@fitlab-ai/agent-infra",
3
+ "version": "0.6.0",
4
+ "type": "module"
5
+ }
@@ -2,18 +2,55 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { execSync } from 'node:child_process';
4
4
  import { platform } from 'node:os';
5
- import { info, ok, err } from './log.js';
6
- import { prompt, select, closePrompt } from './prompt.js';
7
- import { resolveTemplateDir } from './paths.js';
8
- import { renderFile, copySkillDir, KNOWN_PLATFORMS } from './render.js';
9
- import { VERSION } from './version.js';
5
+ import { info, ok, err } from './log.ts';
6
+ import { prompt, select, closePrompt } from './prompt.ts';
7
+ import { resolveTemplateDir } from './paths.ts';
8
+ import { renderFile, copySkillDir, KNOWN_PLATFORMS } from './render.ts';
9
+ import { enginesForPlatform } from './sandbox/engines/index.ts';
10
+ import { VERSION } from './version.ts';
11
+
12
+ type FileRegistry = {
13
+ managed: string[];
14
+ merged: string[];
15
+ ejected: string[];
16
+ };
17
+
18
+ type SourceEntry = {
19
+ type: 'local';
20
+ path: string;
21
+ };
22
+
23
+ type Defaults = {
24
+ files: FileRegistry;
25
+ sandbox: Record<string, unknown>;
26
+ labels: Record<string, unknown>;
27
+ };
28
+
29
+ type AgentConfig = {
30
+ project: string;
31
+ org: string;
32
+ language: string;
33
+ platform: { type: string };
34
+ templateVersion: string;
35
+ sandbox: Record<string, unknown>;
36
+ labels: Record<string, unknown>;
37
+ files: FileRegistry;
38
+ templates?: { sources: SourceEntry[] };
39
+ skills?: { sources: SourceEntry[] };
40
+ };
10
41
 
11
42
  const defaults = JSON.parse(
12
43
  fs.readFileSync(new URL('./defaults.json', import.meta.url), 'utf8')
13
- );
44
+ ) as Defaults;
14
45
 
15
- function isPathOwnedByOtherPlatform(relativePath, platformType) {
16
- const top = String(relativePath || '').replace(/\\/g, '/').replace(/^\.\//, '').split('/')[0];
46
+ const PLATFORM_DEFAULT_ENGINES = Object.freeze({
47
+ linux: 'native',
48
+ darwin: 'colima',
49
+ win32: 'wsl2'
50
+ });
51
+
52
+ function isPathOwnedByOtherPlatform(relativePath: string, platformType: string): boolean {
53
+ const top = String(relativePath || '').replace(/\\/g, '/').replace(/^\.\//, '').split('/')[0] ?? '';
17
54
  if (!top.startsWith('.')) return false;
18
55
 
19
56
  const candidate = top.slice(1);
@@ -21,7 +58,7 @@ function isPathOwnedByOtherPlatform(relativePath, platformType) {
21
58
  return candidate !== platformType;
22
59
  }
23
60
 
24
- function buildDefaultFiles(platformType) {
61
+ function buildDefaultFiles(platformType: string): FileRegistry {
25
62
  return {
26
63
  managed: (defaults.files.managed || []).filter((entry) => !isPathOwnedByOtherPlatform(entry, platformType)),
27
64
  merged: (defaults.files.merged || []).filter((entry) => !isPathOwnedByOtherPlatform(entry, platformType)),
@@ -29,7 +66,7 @@ function buildDefaultFiles(platformType) {
29
66
  };
30
67
  }
31
68
 
32
- function detectProjectName() {
69
+ function detectProjectName(): string {
33
70
  try {
34
71
  const url = execSync('git remote get-url origin', { stdio: ['pipe', 'pipe', 'pipe'] })
35
72
  .toString().trim().replace(/\.git$/, '');
@@ -39,16 +76,16 @@ function detectProjectName() {
39
76
  }
40
77
  }
41
78
 
42
- function detectOrgName() {
79
+ function detectOrgName(): string {
43
80
  try {
44
81
  const url = execSync('git remote get-url origin', { stdio: ['pipe', 'pipe', 'pipe'] })
45
82
  .toString().trim().replace(/\.git$/, '');
46
83
  // SSH: git@github.com:org/repo → org
47
84
  // HTTPS: https://github.com/org/repo → org
48
85
  const sshMatch = url.match(/:([^/]+)\//);
49
- if (sshMatch) return sshMatch[1];
86
+ if (sshMatch?.[1]) return sshMatch[1];
50
87
  const httpsMatch = url.match(/\/\/[^/]+\/([^/]+)\//);
51
- if (httpsMatch) return httpsMatch[1];
88
+ if (httpsMatch?.[1]) return httpsMatch[1];
52
89
  } catch {
53
90
  // no remote
54
91
  }
@@ -57,7 +94,7 @@ function detectOrgName() {
57
94
 
58
95
  const VALID_NAME_RE = /^[a-zA-Z0-9_.@-]+$/;
59
96
 
60
- function parseLocalSources(input) {
97
+ function parseLocalSources(input: string): SourceEntry[] {
61
98
  return input
62
99
  .split(',')
63
100
  .map((entry) => entry.trim())
@@ -65,7 +102,7 @@ function parseLocalSources(input) {
65
102
  .map((entry) => ({ type: 'local', path: entry }));
66
103
  }
67
104
 
68
- async function cmdInit() {
105
+ async function cmdInit(): Promise<void> {
69
106
  console.log('');
70
107
  console.log(' agent-infra init');
71
108
  console.log(' ================================');
@@ -127,12 +164,19 @@ async function cmdInit() {
127
164
  return;
128
165
  }
129
166
 
167
+ const currentPlatform = platform();
168
+ const defaultEngine = PLATFORM_DEFAULT_ENGINES[currentPlatform as keyof typeof PLATFORM_DEFAULT_ENGINES];
169
+ const engineChoices = enginesForPlatform(currentPlatform).sort((left, right) => {
170
+ if (left === defaultEngine) return -1;
171
+ if (right === defaultEngine) return 1;
172
+ return 0;
173
+ });
130
174
  let sandboxEngine = null;
131
- if (platform() === 'darwin') {
175
+ if (engineChoices.length > 0) {
132
176
  sandboxEngine = await select(
133
- 'Sandbox engine (macOS)',
134
- ['colima', 'orbstack', 'docker-desktop'],
135
- 'colima'
177
+ `Sandbox engine (${currentPlatform})`,
178
+ engineChoices,
179
+ defaultEngine
136
180
  );
137
181
  }
138
182
 
@@ -231,7 +275,7 @@ async function cmdInit() {
231
275
  ok('Installed .opencode/commands/update-agent-infra.md');
232
276
 
233
277
  // generate .agents/.airc.json
234
- const config = {
278
+ const config: AgentConfig = {
235
279
  project: projectName,
236
280
  org: orgName,
237
281
  language,
@@ -1,21 +1,21 @@
1
1
  import pc from 'picocolors';
2
2
 
3
- function info(...args) {
3
+ function info(...args: unknown[]) {
4
4
  const msg = args.join(' ');
5
5
  process.stdout.write(` ${pc.bold(pc.blue('>'))} ${msg}\n`);
6
6
  }
7
7
 
8
- function ok(...args) {
8
+ function ok(...args: unknown[]) {
9
9
  const msg = args.join(' ');
10
10
  process.stdout.write(` ${pc.bold(pc.green('\u2713'))} ${msg}\n`);
11
11
  }
12
12
 
13
- function err(...args) {
13
+ function err(...args: unknown[]) {
14
14
  const msg = args.join(' ');
15
15
  process.stderr.write(` ${pc.bold(pc.red('\u2717'))} ${msg}\n`);
16
16
  }
17
17
 
18
- function ask(text) {
18
+ function ask(text: string) {
19
19
  process.stdout.write(` ${pc.bold(pc.yellow('?'))} ${text}`);
20
20
  }
21
21