@adityaaria/agent-os 1.0.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/AGENTS.md +41 -0
- package/AGENT_OS_BOOTSTRAP.md +29 -0
- package/ENTRYPOINT.md +118 -0
- package/LICENSE +21 -0
- package/README.md +230 -0
- package/adapters/antigravity/README.md +9 -0
- package/adapters/antigravity/plugin.json +13 -0
- package/adapters/claude/CLAUDE.md +16 -0
- package/adapters/claude/README.md +10 -0
- package/adapters/codex/README.md +20 -0
- package/adapters/cursor/README.md +9 -0
- package/adapters/cursor/agent-os.rules.md +18 -0
- package/bin/agent-os.mjs +98 -0
- package/core/AUTO_PROJECT_DETECTOR.md +99 -0
- package/core/COMPLIANCE_GATE.md +19 -0
- package/core/FRAMEWORK_AUDITOR.md +35 -0
- package/core/KNOWLEDGE_REGENERATOR.md +30 -0
- package/core/KNOWLEDGE_VALIDATOR.md +29 -0
- package/core/PROJECT_CONTEXT_STANDARD.md +19 -0
- package/core/QUALITY_GATES.md +42 -0
- package/core/REPORTING_STANDARD.md +47 -0
- package/core/SKILL_ORCHESTRATOR.md +63 -0
- package/core/TASK_ROUTER.md +33 -0
- package/core/TRACEABILITY_STANDARD.md +40 -0
- package/package.json +28 -0
- package/scripts/install.mjs +690 -0
- package/scripts/lib/cli-utils.mjs +148 -0
- package/scripts/lib/run-utils.mjs +185 -0
- package/scripts/lib/telegram-audit-log.mjs +50 -0
- package/scripts/lib/telegram-bot-utils.mjs +413 -0
- package/scripts/lib/telegram-utils.mjs +127 -0
- package/scripts/orchestrate.mjs +230 -0
- package/scripts/quality-static.mjs +19 -0
- package/scripts/report-export.mjs +86 -0
- package/scripts/report-notify.mjs +169 -0
- package/scripts/run-execution.mjs +83 -0
- package/scripts/run-list.mjs +44 -0
- package/scripts/run-status.mjs +38 -0
- package/scripts/run-step.mjs +173 -0
- package/scripts/setup-wizard.mjs +116 -0
- package/scripts/sync-runtime.mjs +87 -0
- package/scripts/telegram-bot.mjs +88 -0
- package/scripts/telegram-check.mjs +94 -0
- package/scripts/telegram-poll.mjs +176 -0
- package/scripts/validate-framework.mjs +290 -0
- package/skills/api-contract-audit/SKILL.md +26 -0
- package/skills/api-contract-audit/checklist.md +16 -0
- package/skills/api-contract-audit/examples.md +3 -0
- package/skills/api-contract-audit/report-template.md +39 -0
- package/skills/api-contract-audit/workflow.md +15 -0
- package/skills/audit/SKILL.md +28 -0
- package/skills/audit/checklist.md +18 -0
- package/skills/audit/examples.md +26 -0
- package/skills/audit/report-template.md +39 -0
- package/skills/audit/workflow.md +15 -0
- package/skills/bug-fix/SKILL.md +25 -0
- package/skills/bug-fix/checklist.md +18 -0
- package/skills/bug-fix/examples.md +26 -0
- package/skills/bug-fix/report-template.md +39 -0
- package/skills/bug-fix/workflow.md +10 -0
- package/skills/database-impact-analysis/SKILL.md +27 -0
- package/skills/database-impact-analysis/checklist.md +16 -0
- package/skills/database-impact-analysis/examples.md +3 -0
- package/skills/database-impact-analysis/report-template.md +39 -0
- package/skills/database-impact-analysis/workflow.md +14 -0
- package/skills/enhancement/SKILL.md +27 -0
- package/skills/enhancement/checklist.md +19 -0
- package/skills/enhancement/examples.md +26 -0
- package/skills/enhancement/report-template.md +39 -0
- package/skills/enhancement/workflow.md +10 -0
- package/skills/enterprise-certification/SKILL.md +19 -0
- package/skills/enterprise-certification/checklist.md +17 -0
- package/skills/enterprise-certification/report-template.md +39 -0
- package/skills/enterprise-certification/workflow.md +14 -0
- package/skills/knowledge-evolution/SKILL.md +23 -0
- package/skills/knowledge-evolution/checklist.md +17 -0
- package/skills/knowledge-evolution/report-template.md +39 -0
- package/skills/knowledge-evolution/workflow.md +14 -0
- package/skills/new-page/SKILL.md +26 -0
- package/skills/new-page/checklist.md +21 -0
- package/skills/new-page/examples.md +26 -0
- package/skills/new-page/report-template.md +39 -0
- package/skills/new-page/workflow.md +10 -0
- package/skills/project-onboarding/SKILL.md +27 -0
- package/skills/project-onboarding/checklist.md +18 -0
- package/skills/project-onboarding/examples.md +26 -0
- package/skills/project-onboarding/report-template.md +39 -0
- package/skills/project-onboarding/workflow.md +9 -0
- package/skills/release-readiness/SKILL.md +27 -0
- package/skills/release-readiness/checklist.md +16 -0
- package/skills/release-readiness/examples.md +3 -0
- package/skills/release-readiness/report-template.md +39 -0
- package/skills/release-readiness/workflow.md +14 -0
- package/skills/repository-health/SKILL.md +26 -0
- package/skills/repository-health/checklist.md +20 -0
- package/skills/repository-health/report-template.md +39 -0
- package/skills/repository-health/workflow.md +14 -0
- package/skills/selenium-e2e/SKILL.md +83 -0
- package/skills/selenium-e2e/checklist.md +29 -0
- package/skills/selenium-e2e/examples.md +26 -0
- package/skills/selenium-e2e/report-template.md +39 -0
- package/skills/selenium-e2e/workflow.md +9 -0
- package/skills/self-audit/SKILL.md +19 -0
- package/skills/self-audit/checklist.md +14 -0
- package/skills/self-audit/report-template.md +39 -0
- package/skills/self-audit/workflow.md +11 -0
- package/skills/traceability-analysis/SKILL.md +25 -0
- package/skills/traceability-analysis/checklist.md +17 -0
- package/skills/traceability-analysis/examples.md +3 -0
- package/skills/traceability-analysis/report-template.md +39 -0
- package/skills/traceability-analysis/workflow.md +17 -0
- package/templates/API_RULES.md +20 -0
- package/templates/ARCHITECTURE.md +20 -0
- package/templates/BUSINESS_FLOW.md +20 -0
- package/templates/COMMANDS.md +20 -0
- package/templates/DATABASE_RULES.md +20 -0
- package/templates/DEPENDENCY_MAP.md +20 -0
- package/templates/DEPLOYMENT_RULES.md +20 -0
- package/templates/DOMAIN_MODEL.md +20 -0
- package/templates/FE_BE_TRACEABILITY.md +20 -0
- package/templates/KNOWN_LIMITATIONS.md +20 -0
- package/templates/MODULE_CATALOG.md +20 -0
- package/templates/PROJECT_PROFILE.md +20 -0
- package/templates/RISK_REGISTER.md +20 -0
- package/templates/SECURITY_RULES.md +20 -0
- package/templates/TECHNICAL_DEBT.md +20 -0
- package/templates/TECH_STACK.md +20 -0
- package/templates/TESTING_RULES.md +47 -0
- package/templates/UI_UX_RULES.md +20 -0
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { wantsHelp, showHelp } from './lib/cli-utils.mjs';
|
|
6
|
+
|
|
7
|
+
if (wantsHelp(process.argv)) {
|
|
8
|
+
showHelp({
|
|
9
|
+
command: 'install',
|
|
10
|
+
description: 'Install Agent OS framework into a workspace',
|
|
11
|
+
usage: [
|
|
12
|
+
'agent-os install --workspace-file <file> --all [options]',
|
|
13
|
+
'agent-os install --target <dir> --allow-target-install [options]'
|
|
14
|
+
],
|
|
15
|
+
required: [
|
|
16
|
+
{ flag: '--workspace-file <file>', desc: 'Path to .code-workspace file' }
|
|
17
|
+
],
|
|
18
|
+
options: [
|
|
19
|
+
{ flag: '--all', desc: 'Install all IDE adapters (cursor, claude, codex, antigravity)' },
|
|
20
|
+
{ flag: '--backup', desc: 'Backup existing files before overwriting' },
|
|
21
|
+
{ flag: '--force', desc: 'Force overwrite on conflicts' },
|
|
22
|
+
{ flag: '--dry-run', desc: 'Preview changes without writing files' },
|
|
23
|
+
{ flag: '--verbose', desc: 'Show detailed output' },
|
|
24
|
+
{ flag: '--target <dir>', desc: 'Install to explicit target (dev/test only)' },
|
|
25
|
+
{ flag: '--allow-target-install', desc: 'Required with --target' }
|
|
26
|
+
],
|
|
27
|
+
examples: [
|
|
28
|
+
'agent-os install --workspace-file ./my-project.code-workspace --all --backup',
|
|
29
|
+
'agent-os install --workspace-file ./my-project.code-workspace --all --dry-run'
|
|
30
|
+
]
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
35
|
+
const __dirname = path.dirname(__filename);
|
|
36
|
+
const repoRoot = path.resolve(__dirname, '..');
|
|
37
|
+
|
|
38
|
+
const args = process.argv.slice(2);
|
|
39
|
+
function hasFlag(name) { return args.includes(name); }
|
|
40
|
+
function getArg(name, fallback = null) {
|
|
41
|
+
const idx = args.indexOf(name);
|
|
42
|
+
if (idx === -1) return fallback;
|
|
43
|
+
return args[idx + 1] ?? fallback;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const isDryRun = hasFlag('--dry-run');
|
|
47
|
+
const isVerbose = hasFlag('--verbose');
|
|
48
|
+
const doBackup = hasFlag('--backup') || hasFlag('--force');
|
|
49
|
+
const force = hasFlag('--force');
|
|
50
|
+
|
|
51
|
+
const workspaceFileInput = getArg('--workspace-file', null);
|
|
52
|
+
const hasTargetFlag = hasFlag('--target');
|
|
53
|
+
const allowTargetInstall = hasFlag('--allow-target-install');
|
|
54
|
+
|
|
55
|
+
if (workspaceFileInput && hasTargetFlag) {
|
|
56
|
+
console.error("CRITICAL: Cannot use --target and --workspace-file together.");
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!workspaceFileInput && !allowTargetInstall) {
|
|
61
|
+
console.error("CRITICAL: --workspace-file is required for installation. Use --allow-target-install only for explicit development/test installs.");
|
|
62
|
+
console.error(" Run 'agent-os install --help' for full usage.");
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let target;
|
|
67
|
+
let workspaceMode = false;
|
|
68
|
+
let workspaceData = null;
|
|
69
|
+
let workspaceName = '';
|
|
70
|
+
|
|
71
|
+
if (workspaceFileInput) {
|
|
72
|
+
const absWorkspaceFile = path.resolve(process.cwd(), workspaceFileInput);
|
|
73
|
+
if (!fs.existsSync(absWorkspaceFile)) {
|
|
74
|
+
console.error(`CRITICAL: Workspace file not found: ${absWorkspaceFile}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
const rawContent = fs.readFileSync(absWorkspaceFile, 'utf8');
|
|
78
|
+
const cleanContent = rawContent.replace(/\/\/.*|\/\*[\s\S]*?\*\//g, '');
|
|
79
|
+
let wsJson;
|
|
80
|
+
try {
|
|
81
|
+
wsJson = JSON.parse(cleanContent);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
console.error(`CRITICAL: Failed to parse workspace JSON: ${err.message}`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
if (!wsJson.folders || !Array.isArray(wsJson.folders)) {
|
|
87
|
+
console.error(`CRITICAL: Workspace file missing 'folders' array.`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
workspaceMode = true;
|
|
92
|
+
workspaceName = path.basename(absWorkspaceFile, '.code-workspace');
|
|
93
|
+
const workspaceDir = path.dirname(absWorkspaceFile);
|
|
94
|
+
target = path.join(workspaceDir, '.agent-os-workspace');
|
|
95
|
+
|
|
96
|
+
const resolvedFolders = [];
|
|
97
|
+
for (const f of wsJson.folders) {
|
|
98
|
+
if (f.path) {
|
|
99
|
+
const absPath = path.resolve(workspaceDir, f.path);
|
|
100
|
+
if (fs.existsSync(absPath)) {
|
|
101
|
+
resolvedFolders.push({ name: f.name || path.basename(absPath), path: absPath });
|
|
102
|
+
} else {
|
|
103
|
+
console.warn(`[WARN] Workspace folder not found: ${absPath}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
workspaceData = {
|
|
109
|
+
file: absWorkspaceFile,
|
|
110
|
+
name: workspaceName,
|
|
111
|
+
folders: resolvedFolders,
|
|
112
|
+
json: wsJson
|
|
113
|
+
};
|
|
114
|
+
} else {
|
|
115
|
+
const targetInput = getArg('--target', '.');
|
|
116
|
+
target = path.resolve(process.cwd(), targetInput);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const stats = { copied: 0, skipped: 0, overwritten: 0, backedUp: 0, errors: 0, conflicts: 0, workspaceFileUpdated: false, workspaceFileBackupPath: null, agentOsWorkspaceEntry: null };
|
|
120
|
+
const rollbackLog = [];
|
|
121
|
+
let logStream = null;
|
|
122
|
+
|
|
123
|
+
if (!isDryRun) {
|
|
124
|
+
try {
|
|
125
|
+
if (!fs.existsSync(target)) fs.mkdirSync(target, { recursive: true });
|
|
126
|
+
logStream = fs.createWriteStream(path.join(target, 'install.log'), { flags: 'a' });
|
|
127
|
+
} catch (err) {
|
|
128
|
+
console.error(`CRITICAL: Cannot write to target directory. ${err.message}`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function log(msg, verboseOnly = false) {
|
|
134
|
+
if (verboseOnly && !isVerbose) return;
|
|
135
|
+
const logMsg = `[${new Date().toISOString()}] ${msg}`;
|
|
136
|
+
console.log(msg);
|
|
137
|
+
if (logStream) logStream.write(logMsg + '\n');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function logError(msg) {
|
|
141
|
+
log(`[ERROR] ${msg}`);
|
|
142
|
+
stats.errors++;
|
|
143
|
+
if (!force) {
|
|
144
|
+
log(`[ABORT] Fatal error encountered and --force not provided. Rolling back...`);
|
|
145
|
+
rollback();
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function rollback() {
|
|
151
|
+
log('--- ROLLBACK INITIATED ---');
|
|
152
|
+
if (isDryRun) {
|
|
153
|
+
log('Dry run: rollback is a no-op.');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const rollbackActions = [...rollbackLog];
|
|
157
|
+
rollbackActions.reverse();
|
|
158
|
+
for (const action of rollbackActions) {
|
|
159
|
+
try {
|
|
160
|
+
rollbackAction(action);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
log(`Rollback Failed for ${action.dest}: ${err.message}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
log('--- ROLLBACK COMPLETE ---');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function rollbackAction(action) {
|
|
169
|
+
if (action.type === 'copy') {
|
|
170
|
+
rollbackCopy(action);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (action.type === 'mkdir') rollbackDirectory(action.dest);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function rollbackCopy(action) {
|
|
177
|
+
if (fs.existsSync(action.dest)) {
|
|
178
|
+
fs.unlinkSync(action.dest);
|
|
179
|
+
log(`Rollback: Deleted ${action.dest}`, true);
|
|
180
|
+
}
|
|
181
|
+
if (action.backup) {
|
|
182
|
+
fs.renameSync(action.backup, action.dest);
|
|
183
|
+
log(`Rollback: Restored ${action.dest} from backup`, true);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function rollbackDirectory(dir) {
|
|
188
|
+
if (fs.existsSync(dir) && fs.readdirSync(dir).length === 0) {
|
|
189
|
+
fs.rmdirSync(dir);
|
|
190
|
+
log(`Rollback: Removed empty directory ${dir}`, true);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
log('=== Agent OS v1.0.0 Production Installer ===');
|
|
195
|
+
if (isDryRun) log('MODE: DRY RUN');
|
|
196
|
+
if (workspaceMode) log(`MODE: WORKSPACE (${workspaceName})`);
|
|
197
|
+
|
|
198
|
+
function checkPermissions(dir) {
|
|
199
|
+
try {
|
|
200
|
+
fs.accessSync(dir, fs.constants.W_OK);
|
|
201
|
+
return true;
|
|
202
|
+
} catch (err) {
|
|
203
|
+
log(`Permission check failed for ${dir}: ${err.message}`, true);
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!isDryRun && !checkPermissions(target)) {
|
|
209
|
+
logError(`Target workspace is not writable: ${target}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Version Validation
|
|
213
|
+
const CURRENT_VERSION = "1.0.0";
|
|
214
|
+
const targetVersionFile = path.join(target, '.agent-os', 'VERSION');
|
|
215
|
+
if (fs.existsSync(targetVersionFile)) {
|
|
216
|
+
const existingVersion = fs.readFileSync(targetVersionFile, 'utf8').trim();
|
|
217
|
+
log(`Detected existing installation: v${existingVersion}`);
|
|
218
|
+
if (existingVersion !== CURRENT_VERSION && existingVersion > CURRENT_VERSION) {
|
|
219
|
+
logError(`Downgrade detected! Target has v${existingVersion}, installer is v${CURRENT_VERSION}.`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function ensureDir(dir) {
|
|
224
|
+
if (!fs.existsSync(dir)) {
|
|
225
|
+
if (!isDryRun) fs.mkdirSync(dir, { recursive: true });
|
|
226
|
+
rollbackLog.push({ type: 'mkdir', dest: dir });
|
|
227
|
+
log(`Created directory: ${dir}`, true);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function backupFile(dest) {
|
|
232
|
+
if (fs.existsSync(dest) && doBackup) {
|
|
233
|
+
const backupPath = `${dest}.bak_${Date.now()}`;
|
|
234
|
+
if (!isDryRun) fs.copyFileSync(dest, backupPath);
|
|
235
|
+
log(`Backed up: ${dest} -> ${backupPath}`, true);
|
|
236
|
+
stats.backedUp++;
|
|
237
|
+
return backupPath;
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function copyFile(src, dest) {
|
|
243
|
+
if (!fs.existsSync(src)) {
|
|
244
|
+
logError(`Source does not exist: ${src}`);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
ensureDir(path.dirname(dest));
|
|
248
|
+
|
|
249
|
+
let backupPath = null;
|
|
250
|
+
let isOverwrite = false;
|
|
251
|
+
|
|
252
|
+
if (fs.existsSync(dest)) {
|
|
253
|
+
// Conflict Detection
|
|
254
|
+
const srcStat = fs.statSync(src);
|
|
255
|
+
const destStat = fs.statSync(dest);
|
|
256
|
+
if (destStat.size !== srcStat.size) {
|
|
257
|
+
log(`[CONFLICT] Size mismatch for ${dest}. Source: ${srcStat.size}b, Target: ${destStat.size}b`);
|
|
258
|
+
stats.conflicts++;
|
|
259
|
+
if (!force && !doBackup) {
|
|
260
|
+
logError(`Conflict detected in ${dest}. Use --force to overwrite or --backup to save existing.`);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
backupPath = backupFile(dest);
|
|
265
|
+
isOverwrite = true;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
if (!isDryRun) fs.copyFileSync(src, dest);
|
|
270
|
+
rollbackLog.push({ type: 'copy', dest, backup: backupPath });
|
|
271
|
+
|
|
272
|
+
if (isOverwrite) {
|
|
273
|
+
log(`Overwritten: ${dest}`, true);
|
|
274
|
+
stats.overwritten++;
|
|
275
|
+
} else {
|
|
276
|
+
log(`Copied: ${src} -> ${dest}`, true);
|
|
277
|
+
stats.copied++;
|
|
278
|
+
}
|
|
279
|
+
} catch (err) {
|
|
280
|
+
logError(`Failed to copy ${src} to ${dest}: ${err.message}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function copyRecursive(src, dest) {
|
|
285
|
+
if (!fs.existsSync(src)) {
|
|
286
|
+
logError(`Source does not exist: ${src}`);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const stat = fs.statSync(src);
|
|
290
|
+
if (stat.isDirectory()) {
|
|
291
|
+
ensureDir(dest);
|
|
292
|
+
for (const item of fs.readdirSync(src)) {
|
|
293
|
+
copyRecursive(path.join(src, item), path.join(dest, item));
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
copyFile(src, dest);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function copyIfExists(src, dest) {
|
|
301
|
+
if (fs.existsSync(src)) {
|
|
302
|
+
copyRecursive(src, dest);
|
|
303
|
+
} else {
|
|
304
|
+
log(`Skipped (missing): ${src}`, true);
|
|
305
|
+
stats.skipped++;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function installAgentOS() {
|
|
310
|
+
const out = path.join(target, '.agent-os');
|
|
311
|
+
ensureDir(out);
|
|
312
|
+
if (!isDryRun) fs.writeFileSync(path.join(out, 'VERSION'), CURRENT_VERSION);
|
|
313
|
+
for (const item of ['AGENTS.md', 'ENTRYPOINT.md', 'AGENT_OS_BOOTSTRAP.md', 'plugin.json']) {
|
|
314
|
+
copyIfExists(path.join(repoRoot, item), path.join(out, item));
|
|
315
|
+
}
|
|
316
|
+
for (const dir of ['core', 'skills', 'templates', 'hooks', 'validators']) {
|
|
317
|
+
copyIfExists(path.join(repoRoot, dir), path.join(out, dir));
|
|
318
|
+
}
|
|
319
|
+
ensureDir(path.join(out, 'project'));
|
|
320
|
+
generateProjectKnowledgeBaseline(path.join(out, 'project'));
|
|
321
|
+
generateReportChannelsConfig(path.join(out, 'project'));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function writeIfMissing(filePath, content) {
|
|
325
|
+
if (fs.existsSync(filePath)) {
|
|
326
|
+
stats.skipped++;
|
|
327
|
+
log(`Skipped existing project knowledge: ${filePath}`, true);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
ensureDir(path.dirname(filePath));
|
|
332
|
+
if (!isDryRun) {
|
|
333
|
+
fs.writeFileSync(filePath, content);
|
|
334
|
+
rollbackLog.push({ type: 'copy', dest: filePath, backup: null });
|
|
335
|
+
}
|
|
336
|
+
stats.copied++;
|
|
337
|
+
log(`Generated project knowledge baseline: ${filePath}`, true);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function generateProjectKnowledgeBaseline(projectDir) {
|
|
341
|
+
for (const file of ['PROJECT_PROFILE.md', 'TECH_STACK.md', 'ARCHITECTURE.md']) {
|
|
342
|
+
const source = path.join(repoRoot, 'templates', file);
|
|
343
|
+
const content = fs.existsSync(source)
|
|
344
|
+
? fs.readFileSync(source, 'utf8')
|
|
345
|
+
: `# ${path.basename(file, '.md')}\n\nN/A. Reason: Template source not available.\n`;
|
|
346
|
+
writeIfMissing(path.join(projectDir, file), content);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
writeIfMissing(path.join(projectDir, 'WORKSPACE_MAP.md'), [
|
|
350
|
+
'# Agent OS Workspace Map',
|
|
351
|
+
'',
|
|
352
|
+
'- **Workspace Name**: direct-target-install',
|
|
353
|
+
'- **Workspace File**: N/A. Reason: Installed with explicit target mode.',
|
|
354
|
+
'- **Install Root**: N/A. Reason: Generated during installation.',
|
|
355
|
+
'',
|
|
356
|
+
'## Detected Folders',
|
|
357
|
+
'',
|
|
358
|
+
'N/A. Reason: No `.code-workspace` file was provided for this installation.',
|
|
359
|
+
''
|
|
360
|
+
].join('\n'));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function generateReportChannelsConfig(projectDir) {
|
|
364
|
+
const configPath = path.join(projectDir, 'report-channels.json');
|
|
365
|
+
if (fs.existsSync(configPath)) {
|
|
366
|
+
stats.skipped++;
|
|
367
|
+
log(`Skipped existing report channel config: ${configPath}`, true);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const config = {
|
|
372
|
+
version: 1,
|
|
373
|
+
channels: {
|
|
374
|
+
spreadsheet: {
|
|
375
|
+
enabled: true,
|
|
376
|
+
type: 'file',
|
|
377
|
+
format: 'csv',
|
|
378
|
+
output: 'reports/runs.csv'
|
|
379
|
+
},
|
|
380
|
+
telegram: {
|
|
381
|
+
enabled: false,
|
|
382
|
+
type: 'telegram',
|
|
383
|
+
format: 'json',
|
|
384
|
+
output: 'reports/telegram-summary.json',
|
|
385
|
+
botTokenEnv: 'AGENT_OS_TELEGRAM_BOT_TOKEN',
|
|
386
|
+
chatIdEnv: 'AGENT_OS_TELEGRAM_CHAT_ID'
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
if (!isDryRun) {
|
|
392
|
+
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
393
|
+
rollbackLog.push({ type: 'copy', dest: configPath, backup: null });
|
|
394
|
+
}
|
|
395
|
+
stats.copied++;
|
|
396
|
+
log(`Generated report channel config: ${configPath}`, true);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function readIfExists(file) {
|
|
400
|
+
return fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function hasAnyFile(dir, names) {
|
|
404
|
+
return names.some((name) => fs.existsSync(path.join(dir, name)));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function projectTech(role, pm, framework, languages) {
|
|
408
|
+
return { role, pm, framework, languages };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function roleFromJavaScriptFramework(framework) {
|
|
412
|
+
if (['react', 'vue', 'nextjs'].includes(framework)) return 'frontend';
|
|
413
|
+
if (['nestjs', 'express'].includes(framework)) return 'backend';
|
|
414
|
+
return 'unknown';
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function packageManagerForJavaScript(dir) {
|
|
418
|
+
if (fs.existsSync(path.join(dir, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
419
|
+
if (fs.existsSync(path.join(dir, 'yarn.lock'))) return 'yarn';
|
|
420
|
+
return 'npm';
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function detectJavaScriptProject(dir) {
|
|
424
|
+
if (!fs.existsSync(path.join(dir, 'package.json'))) return null;
|
|
425
|
+
|
|
426
|
+
const languages = ['JavaScript'];
|
|
427
|
+
if (hasAnyFile(dir, ['tsconfig.json'])) languages.push('TypeScript');
|
|
428
|
+
|
|
429
|
+
const pkg = readIfExists(path.join(dir, 'package.json'));
|
|
430
|
+
let framework = 'unknown';
|
|
431
|
+
if (pkg.includes('"next"')) framework = 'nextjs';
|
|
432
|
+
else if (pkg.includes('"react"')) framework = 'react';
|
|
433
|
+
else if (pkg.includes('"vue"')) framework = 'vue';
|
|
434
|
+
else if (pkg.includes('"@nestjs/core"')) framework = 'nestjs';
|
|
435
|
+
else if (pkg.includes('"express"')) framework = 'express';
|
|
436
|
+
|
|
437
|
+
return projectTech(
|
|
438
|
+
roleFromJavaScriptFramework(framework),
|
|
439
|
+
packageManagerForJavaScript(dir),
|
|
440
|
+
framework,
|
|
441
|
+
languages
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function detectPythonProject(dir) {
|
|
446
|
+
if (!hasAnyFile(dir, ['pyproject.toml', 'requirements.txt', 'Pipfile'])) return null;
|
|
447
|
+
|
|
448
|
+
const pythonConfig = [
|
|
449
|
+
readIfExists(path.join(dir, 'pyproject.toml')),
|
|
450
|
+
readIfExists(path.join(dir, 'requirements.txt')),
|
|
451
|
+
readIfExists(path.join(dir, 'Pipfile'))
|
|
452
|
+
].join('\n').toLowerCase();
|
|
453
|
+
|
|
454
|
+
const pm = pythonConfig.includes('poetry') || fs.existsSync(path.join(dir, 'poetry.lock')) ? 'poetry' : 'pip';
|
|
455
|
+
let framework = 'unknown';
|
|
456
|
+
if (pythonConfig.includes('fastapi')) framework = 'fastapi';
|
|
457
|
+
else if (pythonConfig.includes('django')) framework = 'django';
|
|
458
|
+
else if (pythonConfig.includes('flask')) framework = 'flask';
|
|
459
|
+
|
|
460
|
+
return projectTech(framework === 'unknown' ? 'service' : 'backend', pm, framework, ['Python']);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function detectComposerProject(dir) {
|
|
464
|
+
if (!fs.existsSync(path.join(dir, 'composer.json'))) return null;
|
|
465
|
+
|
|
466
|
+
const composer = readIfExists(path.join(dir, 'composer.json')).toLowerCase();
|
|
467
|
+
let framework = 'unknown';
|
|
468
|
+
if (composer.includes('laravel/framework')) framework = 'laravel';
|
|
469
|
+
else if (composer.includes('symfony/')) framework = 'symfony';
|
|
470
|
+
|
|
471
|
+
return projectTech(framework === 'unknown' ? 'service' : 'backend', 'composer', framework, ['PHP']);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function detectRubyProject(dir) {
|
|
475
|
+
if (!hasAnyFile(dir, ['Gemfile', 'gems.rb'])) return null;
|
|
476
|
+
|
|
477
|
+
const gems = [
|
|
478
|
+
readIfExists(path.join(dir, 'Gemfile')),
|
|
479
|
+
readIfExists(path.join(dir, 'gems.rb'))
|
|
480
|
+
].join('\n').toLowerCase();
|
|
481
|
+
const framework = gems.includes('rails') ? 'rails' : 'unknown';
|
|
482
|
+
|
|
483
|
+
return projectTech(framework === 'unknown' ? 'service' : 'backend', 'bundler', framework, ['Ruby']);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function detectJavaProject(dir) {
|
|
487
|
+
if (!hasAnyFile(dir, ['pom.xml', 'build.gradle', 'build.gradle.kts'])) return null;
|
|
488
|
+
|
|
489
|
+
const javaConfig = [
|
|
490
|
+
readIfExists(path.join(dir, 'pom.xml')),
|
|
491
|
+
readIfExists(path.join(dir, 'build.gradle')),
|
|
492
|
+
readIfExists(path.join(dir, 'build.gradle.kts'))
|
|
493
|
+
].join('\n').toLowerCase();
|
|
494
|
+
|
|
495
|
+
const pm = fs.existsSync(path.join(dir, 'pom.xml')) ? 'maven' : 'gradle';
|
|
496
|
+
const framework = javaConfig.includes('spring') ? 'spring' : 'java';
|
|
497
|
+
return projectTech('backend', pm, framework, ['Java']);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function detectDotnetProject(dir) {
|
|
501
|
+
const hasDotnetFile = fs.readdirSync(dir).some((entry) => entry.endsWith('.csproj') || entry.endsWith('.sln'));
|
|
502
|
+
return hasDotnetFile ? projectTech('backend', 'dotnet', 'dotnet', ['C#']) : null;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function detectDartProject(dir) {
|
|
506
|
+
if (!fs.existsSync(path.join(dir, 'pubspec.yaml'))) return null;
|
|
507
|
+
|
|
508
|
+
const pubspec = readIfExists(path.join(dir, 'pubspec.yaml')).toLowerCase();
|
|
509
|
+
const framework = pubspec.includes('flutter:') ? 'flutter' : 'dart';
|
|
510
|
+
return projectTech(framework === 'flutter' ? 'mobile' : 'service', 'pub', framework, ['Dart']);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function guessRoleAndTech(dir) {
|
|
514
|
+
const detectors = [
|
|
515
|
+
detectJavaScriptProject,
|
|
516
|
+
detectPythonProject,
|
|
517
|
+
(projectDir) => fs.existsSync(path.join(projectDir, 'Cargo.toml')) ? projectTech('service', 'cargo', 'rust', ['Rust']) : null,
|
|
518
|
+
detectComposerProject,
|
|
519
|
+
detectRubyProject,
|
|
520
|
+
detectJavaProject,
|
|
521
|
+
(projectDir) => fs.existsSync(path.join(projectDir, 'go.mod')) ? projectTech('backend', 'go mod', 'go', ['Go']) : null,
|
|
522
|
+
detectDotnetProject,
|
|
523
|
+
detectDartProject
|
|
524
|
+
];
|
|
525
|
+
|
|
526
|
+
return detectors.map((detect) => detect(dir)).find(Boolean) ?? projectTech('unknown', 'unknown', 'unknown', []);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function generateWorkspaceMap() {
|
|
530
|
+
if (!workspaceMode || !workspaceData) return;
|
|
531
|
+
const projectDir = path.join(target, '.agent-os', 'project');
|
|
532
|
+
ensureDir(projectDir);
|
|
533
|
+
const mapFile = path.join(projectDir, 'WORKSPACE_MAP.md');
|
|
534
|
+
|
|
535
|
+
let md = `# Agent OS Workspace Map\n\n`;
|
|
536
|
+
md += `- **Workspace Name**: ${workspaceData.name}\n`;
|
|
537
|
+
md += `- **Workspace File**: ${workspaceData.file}\n`;
|
|
538
|
+
md += `- **Install Root**: ${target}\n\n`;
|
|
539
|
+
md += `## Detected Folders\n\n`;
|
|
540
|
+
|
|
541
|
+
for (const f of workspaceData.folders) {
|
|
542
|
+
const tech = guessRoleAndTech(f.path);
|
|
543
|
+
md += `### ${f.name}\n`;
|
|
544
|
+
md += `- **Absolute Path**: ${f.path}\n`;
|
|
545
|
+
md += `- **Probable Role**: ${tech.role}\n`;
|
|
546
|
+
md += `- **Languages**: ${tech.languages.length > 0 ? tech.languages.join(', ') : 'unknown'}\n`;
|
|
547
|
+
md += `- **Package Manager**: ${tech.pm}\n`;
|
|
548
|
+
md += `- **Framework**: ${tech.framework}\n\n`;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (!isDryRun) {
|
|
552
|
+
fs.writeFileSync(mapFile, md);
|
|
553
|
+
rollbackLog.push({ type: 'copy', dest: mapFile, backup: null });
|
|
554
|
+
}
|
|
555
|
+
log(`Generated WORKSPACE_MAP.md for ${workspaceData.name}`, true);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function updateWorkspaceFile() {
|
|
559
|
+
if (!workspaceMode || !workspaceData?.json) return;
|
|
560
|
+
|
|
561
|
+
const absWorkspaceFile = workspaceData.file;
|
|
562
|
+
const workspaceDir = path.dirname(absWorkspaceFile);
|
|
563
|
+
let relativePath = path.relative(workspaceDir, target).split(path.sep).join('/');
|
|
564
|
+
if (relativePath === '') relativePath = '.';
|
|
565
|
+
|
|
566
|
+
const newEntry = { name: "agent-os", path: relativePath };
|
|
567
|
+
|
|
568
|
+
const existing = workspaceData.json.folders.find(f => f.name === newEntry.name || f.path === newEntry.path);
|
|
569
|
+
|
|
570
|
+
if (existing) {
|
|
571
|
+
log(`Agent OS folder already exists in workspace file. Skipping update.`, true);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (isVerbose) {
|
|
576
|
+
log(`[VERBOSE] Workspace folders before update:\n${JSON.stringify(workspaceData.json.folders, null, 2)}`);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
workspaceData.json.folders.push(newEntry);
|
|
580
|
+
|
|
581
|
+
if (isVerbose) {
|
|
582
|
+
log(`[VERBOSE] Workspace folders after update:\n${JSON.stringify(workspaceData.json.folders, null, 2)}`);
|
|
583
|
+
log(`[VERBOSE] Added Agent OS path: ${relativePath}`);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (!isDryRun) {
|
|
587
|
+
let bp = null;
|
|
588
|
+
if (doBackup && fs.existsSync(absWorkspaceFile)) {
|
|
589
|
+
bp = `${absWorkspaceFile}.bak_${Date.now()}`;
|
|
590
|
+
fs.copyFileSync(absWorkspaceFile, bp);
|
|
591
|
+
log(`Backed up workspace file: ${absWorkspaceFile} -> ${bp}`, true);
|
|
592
|
+
stats.workspaceFileBackupPath = bp;
|
|
593
|
+
}
|
|
594
|
+
fs.writeFileSync(absWorkspaceFile, JSON.stringify(workspaceData.json, null, 2));
|
|
595
|
+
rollbackLog.push({ type: 'copy', dest: absWorkspaceFile, backup: bp });
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
log(`Workspace file updated`, true);
|
|
599
|
+
log(`Agent OS folder added to workspace`, true);
|
|
600
|
+
|
|
601
|
+
stats.workspaceFileUpdated = true;
|
|
602
|
+
stats.agentOsWorkspaceEntry = newEntry;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function validateAdapterTarget(adapterDir) {
|
|
606
|
+
if (!fs.existsSync(path.join(target, adapterDir))) {
|
|
607
|
+
log(`[WARN] Target workspace does not seem to contain ${adapterDir}. Installing anyway.`);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function installAdapter(adapter) {
|
|
612
|
+
const map = {
|
|
613
|
+
codex: () => {
|
|
614
|
+
validateAdapterTarget('.codex');
|
|
615
|
+
copyRecursive(path.join(repoRoot, 'skills'), path.join(target, '.codex', 'skills'));
|
|
616
|
+
},
|
|
617
|
+
cursor: () => {
|
|
618
|
+
validateAdapterTarget('.cursor');
|
|
619
|
+
ensureDir(path.join(target, '.cursor', 'rules'));
|
|
620
|
+
copyFile(path.join(repoRoot, 'adapters', 'cursor', 'agent-os.rules.md'), path.join(target, '.cursor', 'rules', 'agent-os.md'));
|
|
621
|
+
},
|
|
622
|
+
claude: () => {
|
|
623
|
+
copyFile(path.join(repoRoot, 'adapters', 'claude', 'CLAUDE.md'), path.join(target, 'CLAUDE.md'));
|
|
624
|
+
},
|
|
625
|
+
antigravity: () => {
|
|
626
|
+
ensureDir(path.join(target, '.antigravity-plugin'));
|
|
627
|
+
copyFile(path.join(repoRoot, 'adapters', 'antigravity', 'plugin.json'), path.join(target, '.antigravity-plugin', 'plugin.json'));
|
|
628
|
+
},
|
|
629
|
+
fallback: () => {
|
|
630
|
+
const dest = path.join(target, '.agent');
|
|
631
|
+
ensureDir(dest);
|
|
632
|
+
const fallbackPath = path.join(dest, 'README.md');
|
|
633
|
+
let bp = null;
|
|
634
|
+
if (!isDryRun) {
|
|
635
|
+
if (fs.existsSync(fallbackPath)) { bp = backupFile(fallbackPath); stats.overwritten++; }
|
|
636
|
+
else { stats.copied++; }
|
|
637
|
+
fs.writeFileSync(fallbackPath, `# Agent OS Fallback\n\nLoad ../.agent-os/ENTRYPOINT.md.\n`);
|
|
638
|
+
rollbackLog.push({ type: 'copy', dest: fallbackPath, backup: bp });
|
|
639
|
+
}
|
|
640
|
+
log(`Generated Universal Fallback: ${fallbackPath}`, true);
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
if (map[adapter]) map[adapter]();
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
try {
|
|
648
|
+
installAgentOS();
|
|
649
|
+
generateWorkspaceMap();
|
|
650
|
+
updateWorkspaceFile();
|
|
651
|
+
|
|
652
|
+
if (hasFlag('--all')) {
|
|
653
|
+
['codex', 'cursor', 'claude', 'antigravity', 'fallback'].forEach(installAdapter);
|
|
654
|
+
} else {
|
|
655
|
+
if (hasFlag('--codex')) installAdapter('codex');
|
|
656
|
+
if (hasFlag('--cursor')) installAdapter('cursor');
|
|
657
|
+
if (hasFlag('--claude')) installAdapter('claude');
|
|
658
|
+
if (hasFlag('--antigravity')) installAdapter('antigravity');
|
|
659
|
+
if (hasFlag('--fallback')) installAdapter('fallback');
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
log('\n=== Installation Summary ===');
|
|
663
|
+
log(`Target: ${target}`);
|
|
664
|
+
if (workspaceMode) {
|
|
665
|
+
log(`Workspace Name: ${workspaceData.name}`);
|
|
666
|
+
log(`Workspace File: ${workspaceData.file}`);
|
|
667
|
+
log(`Resolved Folders: ${workspaceData.folders.length}`);
|
|
668
|
+
}
|
|
669
|
+
log(`Version: v${CURRENT_VERSION}`);
|
|
670
|
+
log(`Copied: ${stats.copied}`);
|
|
671
|
+
log(`Overwritten: ${stats.overwritten}`);
|
|
672
|
+
log(`Backed Up: ${stats.backedUp}`);
|
|
673
|
+
log(`Skipped: ${stats.skipped}`);
|
|
674
|
+
log(`Conflicts: ${stats.conflicts}`);
|
|
675
|
+
log(`Errors: ${stats.errors}`);
|
|
676
|
+
|
|
677
|
+
if (stats.errors > 0 && !force) {
|
|
678
|
+
log('Installation failed due to errors. Rollback should have occurred.');
|
|
679
|
+
} else if (isDryRun) {
|
|
680
|
+
log('SUCCESS (DRY RUN): No files were changed.');
|
|
681
|
+
} else {
|
|
682
|
+
log('SUCCESS: Agent OS installed securely.');
|
|
683
|
+
fs.writeFileSync(path.join(target, 'install-report.json'), JSON.stringify(stats, null, 2));
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
} catch (e) {
|
|
687
|
+
logError(`Unhandled exception during install: ${e.message}`);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (logStream) logStream.end();
|