agdi 3.4.5 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/index.js +2127 -491
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11,8 +11,8 @@ import "./chunk-4VNS5WPM.js";
|
|
|
11
11
|
|
|
12
12
|
// src/index.ts
|
|
13
13
|
import { Command } from "commander";
|
|
14
|
-
import
|
|
15
|
-
import
|
|
14
|
+
import chalk27 from "chalk";
|
|
15
|
+
import ora12 from "ora";
|
|
16
16
|
|
|
17
17
|
// src/core/llm/index.ts
|
|
18
18
|
var PuterProvider = class {
|
|
@@ -2229,6 +2229,14 @@ var BaseAgent = class {
|
|
|
2229
2229
|
${context}`;
|
|
2230
2230
|
}
|
|
2231
2231
|
}
|
|
2232
|
+
if (this.context?.architectureRules && !prompt.includes("# Project Architecture Rules")) {
|
|
2233
|
+
fullPrompt += `
|
|
2234
|
+
|
|
2235
|
+
# Project Architecture Rules
|
|
2236
|
+
CRITICAL: You MUST strictly adhere to the following architectural rules when generating code. Violations will be rejected.
|
|
2237
|
+
|
|
2238
|
+
${this.context.architectureRules}`;
|
|
2239
|
+
}
|
|
2232
2240
|
const response = await Promise.race([
|
|
2233
2241
|
this.llm.generate(fullPrompt, this.getSystemPrompt()),
|
|
2234
2242
|
new Promise(
|
|
@@ -3483,6 +3491,8 @@ URL: ${deployResult.url || "See dashboard"}`,
|
|
|
3483
3491
|
import * as fs7 from "fs/promises";
|
|
3484
3492
|
import * as path7 from "path";
|
|
3485
3493
|
import crypto2 from "crypto";
|
|
3494
|
+
import { exec as execCb } from "child_process";
|
|
3495
|
+
import { promisify as promisify3 } from "util";
|
|
3486
3496
|
|
|
3487
3497
|
// src/context/repository-indexer.ts
|
|
3488
3498
|
import fs6 from "fs/promises";
|
|
@@ -3527,80 +3537,80 @@ var TypeScriptParser = class extends CodeParser {
|
|
|
3527
3537
|
const dependencies = /* @__PURE__ */ new Set();
|
|
3528
3538
|
traverse(ast, {
|
|
3529
3539
|
// Functions
|
|
3530
|
-
FunctionDeclaration(
|
|
3531
|
-
if (
|
|
3540
|
+
FunctionDeclaration(path20) {
|
|
3541
|
+
if (path20.node.id) {
|
|
3532
3542
|
symbols.push({
|
|
3533
|
-
name:
|
|
3543
|
+
name: path20.node.id.name,
|
|
3534
3544
|
type: "function",
|
|
3535
|
-
line:
|
|
3536
|
-
endLine:
|
|
3537
|
-
signature: generateSignature(
|
|
3538
|
-
isExported: isExported(
|
|
3539
|
-
docstring: extractDocstring(
|
|
3545
|
+
line: path20.node.loc?.start.line || 0,
|
|
3546
|
+
endLine: path20.node.loc?.end.line,
|
|
3547
|
+
signature: generateSignature(path20.node),
|
|
3548
|
+
isExported: isExported(path20),
|
|
3549
|
+
docstring: extractDocstring(path20.node)
|
|
3540
3550
|
});
|
|
3541
3551
|
}
|
|
3542
3552
|
},
|
|
3543
3553
|
// Classes
|
|
3544
|
-
ClassDeclaration(
|
|
3545
|
-
if (
|
|
3554
|
+
ClassDeclaration(path20) {
|
|
3555
|
+
if (path20.node.id) {
|
|
3546
3556
|
symbols.push({
|
|
3547
|
-
name:
|
|
3557
|
+
name: path20.node.id.name,
|
|
3548
3558
|
type: "class",
|
|
3549
|
-
line:
|
|
3550
|
-
endLine:
|
|
3551
|
-
signature: `class ${
|
|
3552
|
-
isExported: isExported(
|
|
3553
|
-
docstring: extractDocstring(
|
|
3559
|
+
line: path20.node.loc?.start.line || 0,
|
|
3560
|
+
endLine: path20.node.loc?.end.line,
|
|
3561
|
+
signature: `class ${path20.node.id.name}`,
|
|
3562
|
+
isExported: isExported(path20),
|
|
3563
|
+
docstring: extractDocstring(path20.node)
|
|
3554
3564
|
});
|
|
3555
3565
|
}
|
|
3556
3566
|
},
|
|
3557
3567
|
// Variables (const/let/var)
|
|
3558
|
-
VariableDeclaration(
|
|
3559
|
-
|
|
3568
|
+
VariableDeclaration(path20) {
|
|
3569
|
+
path20.node.declarations.forEach((decl) => {
|
|
3560
3570
|
if (t.isIdentifier(decl.id)) {
|
|
3561
3571
|
symbols.push({
|
|
3562
3572
|
name: decl.id.name,
|
|
3563
3573
|
type: "variable",
|
|
3564
|
-
line:
|
|
3565
|
-
endLine:
|
|
3566
|
-
signature: `${
|
|
3567
|
-
isExported: isExported(
|
|
3568
|
-
docstring: extractDocstring(
|
|
3574
|
+
line: path20.node.loc?.start.line || 0,
|
|
3575
|
+
endLine: path20.node.loc?.end.line,
|
|
3576
|
+
signature: `${path20.node.kind} ${decl.id.name}`,
|
|
3577
|
+
isExported: isExported(path20),
|
|
3578
|
+
docstring: extractDocstring(path20.node)
|
|
3569
3579
|
});
|
|
3570
3580
|
}
|
|
3571
3581
|
});
|
|
3572
3582
|
},
|
|
3573
3583
|
// Interfaces
|
|
3574
|
-
TSInterfaceDeclaration(
|
|
3584
|
+
TSInterfaceDeclaration(path20) {
|
|
3575
3585
|
symbols.push({
|
|
3576
|
-
name:
|
|
3586
|
+
name: path20.node.id.name,
|
|
3577
3587
|
type: "interface",
|
|
3578
|
-
line:
|
|
3579
|
-
endLine:
|
|
3580
|
-
signature: `interface ${
|
|
3581
|
-
isExported: isExported(
|
|
3582
|
-
docstring: extractDocstring(
|
|
3588
|
+
line: path20.node.loc?.start.line || 0,
|
|
3589
|
+
endLine: path20.node.loc?.end.line,
|
|
3590
|
+
signature: `interface ${path20.node.id.name}`,
|
|
3591
|
+
isExported: isExported(path20),
|
|
3592
|
+
docstring: extractDocstring(path20.node)
|
|
3583
3593
|
});
|
|
3584
3594
|
},
|
|
3585
3595
|
// Types
|
|
3586
|
-
TSTypeAliasDeclaration(
|
|
3596
|
+
TSTypeAliasDeclaration(path20) {
|
|
3587
3597
|
symbols.push({
|
|
3588
|
-
name:
|
|
3598
|
+
name: path20.node.id.name,
|
|
3589
3599
|
type: "type",
|
|
3590
|
-
line:
|
|
3591
|
-
endLine:
|
|
3592
|
-
signature: `type ${
|
|
3593
|
-
isExported: isExported(
|
|
3594
|
-
docstring: extractDocstring(
|
|
3600
|
+
line: path20.node.loc?.start.line || 0,
|
|
3601
|
+
endLine: path20.node.loc?.end.line,
|
|
3602
|
+
signature: `type ${path20.node.id.name}`,
|
|
3603
|
+
isExported: isExported(path20),
|
|
3604
|
+
docstring: extractDocstring(path20.node)
|
|
3595
3605
|
});
|
|
3596
3606
|
},
|
|
3597
3607
|
// Imports
|
|
3598
|
-
ImportDeclaration(
|
|
3599
|
-
const source =
|
|
3608
|
+
ImportDeclaration(path20) {
|
|
3609
|
+
const source = path20.node.source.value;
|
|
3600
3610
|
dependencies.add(source);
|
|
3601
3611
|
const names = [];
|
|
3602
3612
|
let isDefault = false;
|
|
3603
|
-
|
|
3613
|
+
path20.node.specifiers.forEach((specifier) => {
|
|
3604
3614
|
if (t.isImportDefaultSpecifier(specifier)) {
|
|
3605
3615
|
isDefault = true;
|
|
3606
3616
|
names.push(specifier.local.name);
|
|
@@ -3618,34 +3628,34 @@ var TypeScriptParser = class extends CodeParser {
|
|
|
3618
3628
|
source,
|
|
3619
3629
|
names,
|
|
3620
3630
|
isDefault,
|
|
3621
|
-
line:
|
|
3631
|
+
line: path20.node.loc?.start.line || 0
|
|
3622
3632
|
});
|
|
3623
3633
|
},
|
|
3624
3634
|
// Exports
|
|
3625
|
-
ExportNamedDeclaration(
|
|
3626
|
-
if (
|
|
3635
|
+
ExportNamedDeclaration(path20) {
|
|
3636
|
+
if (path20.node.declaration) {
|
|
3627
3637
|
} else {
|
|
3628
|
-
|
|
3638
|
+
path20.node.specifiers.forEach((specifier) => {
|
|
3629
3639
|
exports.push({
|
|
3630
3640
|
name: specifier.exported.name,
|
|
3631
3641
|
type: "named",
|
|
3632
|
-
line:
|
|
3642
|
+
line: path20.node.loc?.start.line || 0
|
|
3633
3643
|
});
|
|
3634
3644
|
});
|
|
3635
3645
|
}
|
|
3636
|
-
if (
|
|
3637
|
-
dependencies.add(
|
|
3646
|
+
if (path20.node.source) {
|
|
3647
|
+
dependencies.add(path20.node.source.value);
|
|
3638
3648
|
}
|
|
3639
3649
|
},
|
|
3640
|
-
ExportDefaultDeclaration(
|
|
3650
|
+
ExportDefaultDeclaration(path20) {
|
|
3641
3651
|
let name = "default";
|
|
3642
|
-
if (
|
|
3643
|
-
name =
|
|
3652
|
+
if (path20.node.declaration && path20.node.declaration.id) {
|
|
3653
|
+
name = path20.node.declaration.id.name;
|
|
3644
3654
|
}
|
|
3645
3655
|
exports.push({
|
|
3646
3656
|
name,
|
|
3647
3657
|
type: "default",
|
|
3648
|
-
line:
|
|
3658
|
+
line: path20.node.loc?.start.line || 0
|
|
3649
3659
|
});
|
|
3650
3660
|
}
|
|
3651
3661
|
});
|
|
@@ -3682,8 +3692,8 @@ var TypeScriptParser = class extends CodeParser {
|
|
|
3682
3692
|
return ["ts", "tsx", "js", "jsx", "mjs", "cjs"];
|
|
3683
3693
|
}
|
|
3684
3694
|
};
|
|
3685
|
-
function isExported(
|
|
3686
|
-
return t.isExportDeclaration(
|
|
3695
|
+
function isExported(path20) {
|
|
3696
|
+
return t.isExportDeclaration(path20.parent) || t.isExportNamedDeclaration(path20.parent) || t.isExportDefaultDeclaration(path20.parent);
|
|
3687
3697
|
}
|
|
3688
3698
|
function extractDocstring(node) {
|
|
3689
3699
|
if (node.leadingComments && node.leadingComments.length > 0) {
|
|
@@ -3784,24 +3794,24 @@ var DependencyGraph = class {
|
|
|
3784
3794
|
const cycles = [];
|
|
3785
3795
|
const visited = /* @__PURE__ */ new Set();
|
|
3786
3796
|
const recursionStack = /* @__PURE__ */ new Set();
|
|
3787
|
-
const
|
|
3797
|
+
const path20 = [];
|
|
3788
3798
|
const dfs = (file) => {
|
|
3789
3799
|
visited.add(file);
|
|
3790
3800
|
recursionStack.add(file);
|
|
3791
|
-
|
|
3801
|
+
path20.push(file);
|
|
3792
3802
|
const node = this.nodes.get(file);
|
|
3793
3803
|
if (node) {
|
|
3794
3804
|
for (const dep of node.imports) {
|
|
3795
3805
|
if (!visited.has(dep)) {
|
|
3796
3806
|
dfs(dep);
|
|
3797
3807
|
} else if (recursionStack.has(dep)) {
|
|
3798
|
-
const cycleStart =
|
|
3799
|
-
const cycle =
|
|
3808
|
+
const cycleStart = path20.indexOf(dep);
|
|
3809
|
+
const cycle = path20.slice(cycleStart);
|
|
3800
3810
|
cycles.push([...cycle, dep]);
|
|
3801
3811
|
}
|
|
3802
3812
|
}
|
|
3803
3813
|
}
|
|
3804
|
-
|
|
3814
|
+
path20.pop();
|
|
3805
3815
|
recursionStack.delete(file);
|
|
3806
3816
|
};
|
|
3807
3817
|
for (const file of this.nodes.keys()) {
|
|
@@ -3845,8 +3855,8 @@ var DependencyGraph = class {
|
|
|
3845
3855
|
/**
|
|
3846
3856
|
* Normalize path (remove .., etc.)
|
|
3847
3857
|
*/
|
|
3848
|
-
normalizePath(
|
|
3849
|
-
const parts =
|
|
3858
|
+
normalizePath(path20) {
|
|
3859
|
+
const parts = path20.split("/");
|
|
3850
3860
|
const result = [];
|
|
3851
3861
|
for (const part of parts) {
|
|
3852
3862
|
if (part === "..") {
|
|
@@ -4057,22 +4067,22 @@ var VectorStore = class {
|
|
|
4057
4067
|
/**
|
|
4058
4068
|
* Get stored hash for a file
|
|
4059
4069
|
*/
|
|
4060
|
-
async getFileHash(
|
|
4070
|
+
async getFileHash(path20) {
|
|
4061
4071
|
await this.initialize();
|
|
4062
4072
|
if (!this.db) return null;
|
|
4063
|
-
const row = this.db.prepare("SELECT hash FROM indexed_files WHERE path = ?").get(
|
|
4073
|
+
const row = this.db.prepare("SELECT hash FROM indexed_files WHERE path = ?").get(path20);
|
|
4064
4074
|
return row ? row.hash : null;
|
|
4065
4075
|
}
|
|
4066
4076
|
/**
|
|
4067
4077
|
* Set/Update hash for a file
|
|
4068
4078
|
*/
|
|
4069
|
-
async setFileHash(
|
|
4079
|
+
async setFileHash(path20, hash) {
|
|
4070
4080
|
await this.initialize();
|
|
4071
4081
|
if (!this.db) return;
|
|
4072
4082
|
this.db.prepare(`
|
|
4073
4083
|
INSERT OR REPLACE INTO indexed_files (path, hash, last_indexed)
|
|
4074
4084
|
VALUES (?, ?, ?)
|
|
4075
|
-
`).run(
|
|
4085
|
+
`).run(path20, hash, Date.now());
|
|
4076
4086
|
}
|
|
4077
4087
|
/**
|
|
4078
4088
|
* Add embeddings to store
|
|
@@ -4363,8 +4373,8 @@ var RepositoryIndexer = class {
|
|
|
4363
4373
|
for (const file of files) {
|
|
4364
4374
|
try {
|
|
4365
4375
|
const content = await fs6.readFile(file, "utf-8");
|
|
4366
|
-
const
|
|
4367
|
-
const hash =
|
|
4376
|
+
const crypto4 = await import("crypto");
|
|
4377
|
+
const hash = crypto4.createHash("md5").update(content).digest("hex");
|
|
4368
4378
|
const existingHash = await this.store.getFileHash(file);
|
|
4369
4379
|
if (existingHash === hash) {
|
|
4370
4380
|
skipped++;
|
|
@@ -4498,6 +4508,7 @@ ${symbol.docstring || ""}`);
|
|
|
4498
4508
|
};
|
|
4499
4509
|
|
|
4500
4510
|
// src/agents/core/squad-orchestrator.ts
|
|
4511
|
+
import * as readline from "readline";
|
|
4501
4512
|
var SquadOrchestrator = class {
|
|
4502
4513
|
manager;
|
|
4503
4514
|
frontend;
|
|
@@ -4524,6 +4535,7 @@ var SquadOrchestrator = class {
|
|
|
4524
4535
|
maxRetries: 3,
|
|
4525
4536
|
parallel: true,
|
|
4526
4537
|
autoDeploy: false,
|
|
4538
|
+
breakpoints: false,
|
|
4527
4539
|
...config
|
|
4528
4540
|
};
|
|
4529
4541
|
const agentOptions = { verbose: this.config.verbose };
|
|
@@ -4546,135 +4558,143 @@ var SquadOrchestrator = class {
|
|
|
4546
4558
|
this.log("\u{1F680} Agdi Squad Activated!", "header");
|
|
4547
4559
|
this.log(`Goal: "${userPrompt}"`, "info");
|
|
4548
4560
|
try {
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4561
|
+
const didStash = await this.stashUserChanges();
|
|
4562
|
+
try {
|
|
4563
|
+
this.log("\u{1F4CB} Phase 1: Planning", "phase");
|
|
4564
|
+
const memoryContext = await this.retrieveRepositoryContext(userPrompt);
|
|
4565
|
+
const userPromptWithMemory = memoryContext ? `${userPrompt}
|
|
4552
4566
|
|
|
4553
4567
|
${memoryContext}` : userPrompt;
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
attempts
|
|
4573
|
-
|
|
4568
|
+
const projectSpec = await this.manager.analyzeRequest(userPromptWithMemory);
|
|
4569
|
+
this.tasks = this.manager.generateTasks(projectSpec);
|
|
4570
|
+
this.context = {
|
|
4571
|
+
projectSpec,
|
|
4572
|
+
workspaceRoot: this.config.workspaceRoot,
|
|
4573
|
+
tasks: this.tasks,
|
|
4574
|
+
sharedMemory: /* @__PURE__ */ new Map([["repo_context", memoryContext]]),
|
|
4575
|
+
architectureRules: await this.readArchitectureRules()
|
|
4576
|
+
};
|
|
4577
|
+
this.manager.setContext(this.context);
|
|
4578
|
+
this.frontend.setContext(this.context);
|
|
4579
|
+
this.backend.setContext(this.context);
|
|
4580
|
+
this.qa.setContext(this.context);
|
|
4581
|
+
this.devops.setContext(this.context);
|
|
4582
|
+
this.log(`Created ${this.tasks.length} tasks`, "success");
|
|
4583
|
+
this.log("\u26A1 Phase 2: Execution & Auto-Repair", "phase");
|
|
4584
|
+
let attempts = 0;
|
|
4585
|
+
let healthy = false;
|
|
4586
|
+
while (attempts < this.config.maxRetries && !healthy) {
|
|
4587
|
+
attempts++;
|
|
4588
|
+
this.log(`
|
|
4574
4589
|
\u{1F504} Cycle ${attempts}/${this.config.maxRetries}`, "phase");
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
dependencies: [],
|
|
4585
|
-
priority: "high",
|
|
4586
|
-
state: "pending",
|
|
4587
|
-
createdAt: Date.now(),
|
|
4588
|
-
retryCount: 0
|
|
4589
|
-
};
|
|
4590
|
-
}
|
|
4591
|
-
const qaResult = await this.executeTask(qaTask);
|
|
4592
|
-
tasksSummary.push({
|
|
4593
|
-
id: qaTask.id,
|
|
4594
|
-
title: qaTask.title,
|
|
4595
|
-
assignee: qaTask.assignee,
|
|
4596
|
-
status: qaResult.success ? "completed" : "failed",
|
|
4597
|
-
duration: 0
|
|
4598
|
-
// Placeholder
|
|
4599
|
-
});
|
|
4600
|
-
if (qaResult.files) {
|
|
4601
|
-
await this.writeFiles(qaResult.files, filesCreated, filesModified);
|
|
4602
|
-
}
|
|
4603
|
-
if (qaResult.success) {
|
|
4604
|
-
this.log("\u2728 QA Passed - Codebase is healthy", "success");
|
|
4605
|
-
healthy = true;
|
|
4606
|
-
} else {
|
|
4607
|
-
this.log(`\u26A0\uFE0F QA Found Issues: ${qaResult.errors?.length || 0} errors`, "warn");
|
|
4608
|
-
if (qaResult.errors) {
|
|
4609
|
-
qaResult.errors.forEach((e) => this.log(` - ${e}`, "error"));
|
|
4610
|
-
}
|
|
4611
|
-
if (attempts < this.config.maxRetries) {
|
|
4612
|
-
this.log("\u{1F6E0}\uFE0F Generating Fix Plan...", "info");
|
|
4613
|
-
const fixPrompt = `QA found these issues:
|
|
4614
|
-
${qaResult.errors?.join("\n")}
|
|
4615
|
-
|
|
4616
|
-
Create specific tasks to fix these errors.`;
|
|
4617
|
-
const fixTask = {
|
|
4618
|
-
id: `fix-${attempts}`,
|
|
4619
|
-
title: `Fix Issues from Cycle ${attempts}`,
|
|
4620
|
-
description: `Fix the following errors:
|
|
4621
|
-
${qaResult.errors?.join("\n")}`,
|
|
4622
|
-
assignee: "frontend",
|
|
4623
|
-
// Defaulting to frontend for now, ideal would be to detect
|
|
4590
|
+
await this.executeTasks(filesCreated, filesModified, tasksSummary, errors);
|
|
4591
|
+
this.log("\u{1F575}\uFE0F QA Check", "task");
|
|
4592
|
+
let qaTask = this.tasks.find((t2) => t2.assignee === "qa" && !this.completedTasks.has(t2.id));
|
|
4593
|
+
if (!qaTask) {
|
|
4594
|
+
qaTask = {
|
|
4595
|
+
id: `qa-${attempts}`,
|
|
4596
|
+
title: `Quality Assurance Cycle ${attempts}`,
|
|
4597
|
+
description: "Review codebase for errors, missing features, and type safety.",
|
|
4598
|
+
assignee: "qa",
|
|
4624
4599
|
dependencies: [],
|
|
4625
|
-
priority: "
|
|
4600
|
+
priority: "high",
|
|
4626
4601
|
state: "pending",
|
|
4627
4602
|
createdAt: Date.now(),
|
|
4628
4603
|
retryCount: 0
|
|
4629
4604
|
};
|
|
4630
|
-
this.tasks.push(fixTask);
|
|
4631
|
-
this.log(` + Added repair task: ${fixTask.title}`, "info");
|
|
4632
|
-
}
|
|
4633
|
-
}
|
|
4634
|
-
}
|
|
4635
|
-
if (!healthy) {
|
|
4636
|
-
this.log("\u26A0\uFE0F Max retries reached. Proceeding with best-effort.", "warn");
|
|
4637
|
-
} else {
|
|
4638
|
-
this.log("\u2705 Codebase is stable.", "success");
|
|
4639
|
-
}
|
|
4640
|
-
let deploymentUrl;
|
|
4641
|
-
if (healthy && this.config.autoDeploy) {
|
|
4642
|
-
this.log("\u{1F680} Phase 4: Deployment", "phase");
|
|
4643
|
-
const devopsTask = this.tasks.find((t2) => t2.assignee === "devops");
|
|
4644
|
-
if (devopsTask) {
|
|
4645
|
-
const deployResult = await this.executeTask(devopsTask);
|
|
4646
|
-
if (deployResult.success) {
|
|
4647
|
-
const urlMatch = deployResult.content.match(/https:\/\/[^\s]+/);
|
|
4648
|
-
deploymentUrl = urlMatch ? urlMatch[0] : void 0;
|
|
4649
4605
|
}
|
|
4606
|
+
const qaResult = await this.executeTask(qaTask);
|
|
4650
4607
|
tasksSummary.push({
|
|
4651
|
-
id:
|
|
4652
|
-
title:
|
|
4653
|
-
assignee:
|
|
4654
|
-
status:
|
|
4655
|
-
duration:
|
|
4608
|
+
id: qaTask.id,
|
|
4609
|
+
title: qaTask.title,
|
|
4610
|
+
assignee: qaTask.assignee,
|
|
4611
|
+
status: qaResult.success ? "completed" : "failed",
|
|
4612
|
+
duration: 0
|
|
4613
|
+
// Placeholder
|
|
4656
4614
|
});
|
|
4615
|
+
if (qaResult.files) {
|
|
4616
|
+
await this.writeFiles(qaResult.files, filesCreated, filesModified);
|
|
4617
|
+
}
|
|
4618
|
+
if (qaResult.success) {
|
|
4619
|
+
this.log("\u2728 QA Passed - Codebase is healthy", "success");
|
|
4620
|
+
healthy = true;
|
|
4621
|
+
} else {
|
|
4622
|
+
this.log(`\u26A0\uFE0F QA Found Issues: ${qaResult.errors?.length || 0} errors`, "warn");
|
|
4623
|
+
if (qaResult.errors) {
|
|
4624
|
+
qaResult.errors.forEach((e) => this.log(` - ${e}`, "error"));
|
|
4625
|
+
}
|
|
4626
|
+
if (attempts < this.config.maxRetries) {
|
|
4627
|
+
this.log("\u{1F6E0}\uFE0F Generating Fix Plan...", "info");
|
|
4628
|
+
const fixPrompt = `QA found these issues:
|
|
4629
|
+
${qaResult.errors?.join("\n")}
|
|
4630
|
+
|
|
4631
|
+
Create specific tasks to fix these errors.`;
|
|
4632
|
+
const fixTask = {
|
|
4633
|
+
id: `fix-${attempts}`,
|
|
4634
|
+
title: `Fix Issues from Cycle ${attempts}`,
|
|
4635
|
+
description: `Fix the following errors:
|
|
4636
|
+
${qaResult.errors?.join("\n")}`,
|
|
4637
|
+
assignee: "frontend",
|
|
4638
|
+
// Defaulting to frontend for now, ideal would be to detect
|
|
4639
|
+
dependencies: [],
|
|
4640
|
+
priority: "critical",
|
|
4641
|
+
state: "pending",
|
|
4642
|
+
createdAt: Date.now(),
|
|
4643
|
+
retryCount: 0
|
|
4644
|
+
};
|
|
4645
|
+
this.tasks.push(fixTask);
|
|
4646
|
+
this.log(` + Added repair task: ${fixTask.title}`, "info");
|
|
4647
|
+
}
|
|
4648
|
+
}
|
|
4649
|
+
}
|
|
4650
|
+
if (!healthy) {
|
|
4651
|
+
this.log("\u26A0\uFE0F Max retries reached. Proceeding with best-effort.", "warn");
|
|
4652
|
+
} else {
|
|
4653
|
+
this.log("\u2705 Codebase is stable.", "success");
|
|
4654
|
+
}
|
|
4655
|
+
let deploymentUrl;
|
|
4656
|
+
if (healthy && this.config.autoDeploy) {
|
|
4657
|
+
this.log("\u{1F680} Phase 4: Deployment", "phase");
|
|
4658
|
+
const devopsTask = this.tasks.find((t2) => t2.assignee === "devops");
|
|
4659
|
+
if (devopsTask) {
|
|
4660
|
+
const deployResult = await this.executeTask(devopsTask);
|
|
4661
|
+
if (deployResult.success) {
|
|
4662
|
+
const urlMatch = deployResult.content.match(/https:\/\/[^\s]+/);
|
|
4663
|
+
deploymentUrl = urlMatch ? urlMatch[0] : void 0;
|
|
4664
|
+
}
|
|
4665
|
+
tasksSummary.push({
|
|
4666
|
+
id: devopsTask.id,
|
|
4667
|
+
title: devopsTask.title,
|
|
4668
|
+
assignee: devopsTask.assignee,
|
|
4669
|
+
status: deployResult.success ? "completed" : "failed",
|
|
4670
|
+
duration: Date.now() - startTime
|
|
4671
|
+
});
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4674
|
+
const duration = Date.now() - startTime;
|
|
4675
|
+
this.log("\u{1F389} Squad Mission Complete!", "header");
|
|
4676
|
+
this.log(`Duration: ${(duration / 1e3).toFixed(1)}s`, "info");
|
|
4677
|
+
this.log(`Files created: ${filesCreated.length}`, "info");
|
|
4678
|
+
if (deploymentUrl) {
|
|
4679
|
+
this.log(`Live URL: ${deploymentUrl}`, "success");
|
|
4680
|
+
}
|
|
4681
|
+
const result = {
|
|
4682
|
+
success: errors.length === 0,
|
|
4683
|
+
projectSpec,
|
|
4684
|
+
filesCreated,
|
|
4685
|
+
filesModified,
|
|
4686
|
+
deploymentUrl,
|
|
4687
|
+
duration,
|
|
4688
|
+
tasksSummary,
|
|
4689
|
+
errors
|
|
4690
|
+
};
|
|
4691
|
+
await this.finalizeRun(result);
|
|
4692
|
+
return result;
|
|
4693
|
+
} finally {
|
|
4694
|
+
if (didStash) {
|
|
4695
|
+
await this.popUserStash();
|
|
4657
4696
|
}
|
|
4658
4697
|
}
|
|
4659
|
-
const duration = Date.now() - startTime;
|
|
4660
|
-
this.log("\u{1F389} Squad Mission Complete!", "header");
|
|
4661
|
-
this.log(`Duration: ${(duration / 1e3).toFixed(1)}s`, "info");
|
|
4662
|
-
this.log(`Files created: ${filesCreated.length}`, "info");
|
|
4663
|
-
if (deploymentUrl) {
|
|
4664
|
-
this.log(`Live URL: ${deploymentUrl}`, "success");
|
|
4665
|
-
}
|
|
4666
|
-
const result = {
|
|
4667
|
-
success: errors.length === 0,
|
|
4668
|
-
projectSpec,
|
|
4669
|
-
filesCreated,
|
|
4670
|
-
filesModified,
|
|
4671
|
-
deploymentUrl,
|
|
4672
|
-
duration,
|
|
4673
|
-
tasksSummary,
|
|
4674
|
-
errors
|
|
4675
|
-
};
|
|
4676
|
-
await this.finalizeRun(result);
|
|
4677
|
-
return result;
|
|
4678
4698
|
} catch (error) {
|
|
4679
4699
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
4680
4700
|
this.log(`Mission failed: ${errorMsg}`, "error");
|
|
@@ -4714,7 +4734,10 @@ ${qaResult.errors?.join("\n")}`,
|
|
|
4714
4734
|
const result = await this.executeTask(task);
|
|
4715
4735
|
if (result.files) {
|
|
4716
4736
|
await this.writeFiles(result.files, filesCreated, filesModified);
|
|
4737
|
+
const writtenPaths = result.files.map((f) => f.path);
|
|
4738
|
+
await this.commitTaskChanges(task, writtenPaths);
|
|
4717
4739
|
}
|
|
4740
|
+
await this.waitForBreakpoint(task, result);
|
|
4718
4741
|
this.completedTasks.set(task.id, result);
|
|
4719
4742
|
tasksSummary.push({
|
|
4720
4743
|
id: task.id,
|
|
@@ -5110,6 +5133,221 @@ ${qaResult.errors?.join("\n")}`,
|
|
|
5110
5133
|
});
|
|
5111
5134
|
return diffPayload;
|
|
5112
5135
|
}
|
|
5136
|
+
// ==================== ARCHITECTURE ENFORCEMENT ====================
|
|
5137
|
+
/**
|
|
5138
|
+
* Read project architecture rules from ARCHITECTURE.md or .agdi/architecture.md.
|
|
5139
|
+
* Returns the file contents if found, or undefined if no architecture file exists.
|
|
5140
|
+
*/
|
|
5141
|
+
async readArchitectureRules() {
|
|
5142
|
+
const candidates = [
|
|
5143
|
+
path7.join(this.config.workspaceRoot, "ARCHITECTURE.md"),
|
|
5144
|
+
path7.join(this.config.workspaceRoot, ".agdi", "architecture.md")
|
|
5145
|
+
];
|
|
5146
|
+
for (const candidate of candidates) {
|
|
5147
|
+
try {
|
|
5148
|
+
const content = await fs7.readFile(candidate, "utf-8");
|
|
5149
|
+
if (content.trim().length > 0) {
|
|
5150
|
+
this.log(`\u{1F4D0} Loaded architecture rules from ${path7.relative(this.config.workspaceRoot, candidate)}`, "success");
|
|
5151
|
+
await this.appendTrace("architecture_rules_loaded", {
|
|
5152
|
+
source: path7.relative(this.config.workspaceRoot, candidate),
|
|
5153
|
+
length: content.length
|
|
5154
|
+
});
|
|
5155
|
+
return content;
|
|
5156
|
+
}
|
|
5157
|
+
} catch {
|
|
5158
|
+
}
|
|
5159
|
+
}
|
|
5160
|
+
this.log("No ARCHITECTURE.md found \u2014 agents will use default patterns.", "info");
|
|
5161
|
+
return void 0;
|
|
5162
|
+
}
|
|
5163
|
+
// ==================== GIT STASH SAFETY ====================
|
|
5164
|
+
/**
|
|
5165
|
+
* Stash any uncommitted user changes before squad execution.
|
|
5166
|
+
* This prevents the micro-commit feature from accidentally
|
|
5167
|
+
* including user's staged/unstaged work in agent commits.
|
|
5168
|
+
* Returns true if a stash was created.
|
|
5169
|
+
*/
|
|
5170
|
+
async stashUserChanges() {
|
|
5171
|
+
const execAsync7 = promisify3(execCb);
|
|
5172
|
+
try {
|
|
5173
|
+
const { stdout: status } = await execAsync7("git status --porcelain", {
|
|
5174
|
+
cwd: this.config.workspaceRoot
|
|
5175
|
+
});
|
|
5176
|
+
if (!status.trim()) {
|
|
5177
|
+
return false;
|
|
5178
|
+
}
|
|
5179
|
+
const stashMsg = `agdi-squad-${this.runId}`;
|
|
5180
|
+
await execAsync7(`git stash push --include-untracked -m "${stashMsg}"`, {
|
|
5181
|
+
cwd: this.config.workspaceRoot
|
|
5182
|
+
});
|
|
5183
|
+
this.log(`\u{1F4E6} Stashed ${status.trim().split("\\n").length} uncommitted change(s) for safety`, "info");
|
|
5184
|
+
await this.appendTrace("stash_created", { message: stashMsg, changes: status.trim().split("\\n").length });
|
|
5185
|
+
return true;
|
|
5186
|
+
} catch {
|
|
5187
|
+
return false;
|
|
5188
|
+
}
|
|
5189
|
+
}
|
|
5190
|
+
/**
|
|
5191
|
+
* Pop the user's stashed changes after squad execution completes.
|
|
5192
|
+
*/
|
|
5193
|
+
async popUserStash() {
|
|
5194
|
+
const execAsync7 = promisify3(execCb);
|
|
5195
|
+
try {
|
|
5196
|
+
const stashMsg = `agdi-squad-${this.runId}`;
|
|
5197
|
+
const { stdout: stashList } = await execAsync7("git stash list", {
|
|
5198
|
+
cwd: this.config.workspaceRoot
|
|
5199
|
+
});
|
|
5200
|
+
const lines = stashList.trim().split("\\n");
|
|
5201
|
+
const ourStash = lines.findIndex((l) => l.includes(stashMsg));
|
|
5202
|
+
if (ourStash >= 0) {
|
|
5203
|
+
await execAsync7(`git stash pop stash@{${ourStash}}`, {
|
|
5204
|
+
cwd: this.config.workspaceRoot
|
|
5205
|
+
});
|
|
5206
|
+
this.log("\u{1F4E6} Restored your stashed changes.", "success");
|
|
5207
|
+
await this.appendTrace("stash_restored", { stashIndex: ourStash });
|
|
5208
|
+
}
|
|
5209
|
+
} catch {
|
|
5210
|
+
this.log("\u26A0 Could not restore stashed changes. Run `git stash pop` manually.", "warn");
|
|
5211
|
+
}
|
|
5212
|
+
}
|
|
5213
|
+
// ==================== REVERSIBLE MICRO-COMMITS ====================
|
|
5214
|
+
/**
|
|
5215
|
+
* Atomically commit files generated by a single task.
|
|
5216
|
+
* This creates a granular git history that allows developers to
|
|
5217
|
+
* easily `git revert` any specific AI agent misstep.
|
|
5218
|
+
*
|
|
5219
|
+
* Failures are non-fatal — if git is unavailable or the workspace
|
|
5220
|
+
* is not a repository, the build continues normally.
|
|
5221
|
+
*/
|
|
5222
|
+
async commitTaskChanges(task, filePaths) {
|
|
5223
|
+
if (filePaths.length === 0) return;
|
|
5224
|
+
const execAsync7 = promisify3(execCb);
|
|
5225
|
+
const cwd = this.config.workspaceRoot;
|
|
5226
|
+
try {
|
|
5227
|
+
await execAsync7("git rev-parse --is-inside-work-tree", { cwd });
|
|
5228
|
+
const relativePaths = filePaths.map((fp) => {
|
|
5229
|
+
const abs = path7.resolve(cwd, fp);
|
|
5230
|
+
return path7.relative(cwd, abs);
|
|
5231
|
+
});
|
|
5232
|
+
const BATCH_SIZE = 50;
|
|
5233
|
+
for (let i = 0; i < relativePaths.length; i += BATCH_SIZE) {
|
|
5234
|
+
const batch = relativePaths.slice(i, i + BATCH_SIZE);
|
|
5235
|
+
const escaped = batch.map((p) => `"${p.replace(/"/g, '\\"')}"`).join(" ");
|
|
5236
|
+
await execAsync7(`git add ${escaped}`, { cwd });
|
|
5237
|
+
}
|
|
5238
|
+
const { stdout: staged } = await execAsync7("git diff --cached --name-only", { cwd });
|
|
5239
|
+
if (!staged.trim()) {
|
|
5240
|
+
this.log("No staged changes \u2014 skipping micro-commit", "info");
|
|
5241
|
+
return;
|
|
5242
|
+
}
|
|
5243
|
+
const scope = task.assignee;
|
|
5244
|
+
const subject = task.title.length > 72 ? task.title.substring(0, 69) + "..." : task.title;
|
|
5245
|
+
const commitMsg = `feat(${scope}): ${subject}`;
|
|
5246
|
+
const commitBody = [
|
|
5247
|
+
"Auto-generated by Agdi Squad (Reversible Micro-Commit)",
|
|
5248
|
+
"",
|
|
5249
|
+
`Task ID : ${task.id}`,
|
|
5250
|
+
`Agent : ${task.assignee}`,
|
|
5251
|
+
`Priority: ${task.priority}`,
|
|
5252
|
+
`Run ID : ${this.runId}`
|
|
5253
|
+
].join("\n");
|
|
5254
|
+
await execAsync7(
|
|
5255
|
+
`git commit -m "${commitMsg.replace(/"/g, '\\"')}" -m "${commitBody.replace(/"/g, '\\"')}"`,
|
|
5256
|
+
{ cwd }
|
|
5257
|
+
);
|
|
5258
|
+
this.log(`\u{1F4CC} Micro-commit: ${commitMsg}`, "success");
|
|
5259
|
+
await this.appendTrace("micro_commit", {
|
|
5260
|
+
taskId: task.id,
|
|
5261
|
+
message: commitMsg,
|
|
5262
|
+
files: relativePaths
|
|
5263
|
+
});
|
|
5264
|
+
} catch (err) {
|
|
5265
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5266
|
+
this.log(`Micro-commit skipped: ${msg.split("\n")[0]}`, "warn");
|
|
5267
|
+
}
|
|
5268
|
+
}
|
|
5269
|
+
// ==================== AI BREAKPOINTS ====================
|
|
5270
|
+
/**
|
|
5271
|
+
* Pause execution after a task completes so the developer can
|
|
5272
|
+
* review generated files before the next agent takes over.
|
|
5273
|
+
*
|
|
5274
|
+
* Actions:
|
|
5275
|
+
* Enter → continue to the next task
|
|
5276
|
+
* 'skip' → disable breakpoints for the remainder of the run
|
|
5277
|
+
* 'abort' → halt the entire squad pipeline immediately
|
|
5278
|
+
*/
|
|
5279
|
+
async waitForBreakpoint(task, result) {
|
|
5280
|
+
if (!this.config.breakpoints) return;
|
|
5281
|
+
const { emitAgentEvent: emitAgentEvent2 } = await import("./event-bus-MO5SFUME.js");
|
|
5282
|
+
emitAgentEvent2({
|
|
5283
|
+
type: "breakpoint",
|
|
5284
|
+
agentName: task.assignee,
|
|
5285
|
+
role: task.assignee,
|
|
5286
|
+
message: `Breakpoint hit after task: ${task.title}`,
|
|
5287
|
+
metadata: {
|
|
5288
|
+
taskId: task.id,
|
|
5289
|
+
files: result.files?.map((f) => f.path) ?? [],
|
|
5290
|
+
success: result.success
|
|
5291
|
+
}
|
|
5292
|
+
});
|
|
5293
|
+
console.log("");
|
|
5294
|
+
console.log(chalk11.cyan.bold(`\u23F8 AI Breakpoint \u2014 Task Complete`));
|
|
5295
|
+
console.log(chalk11.gray(` Agent : ${task.assignee}`));
|
|
5296
|
+
console.log(chalk11.gray(` Task : ${task.title}`));
|
|
5297
|
+
console.log(chalk11.gray(` Status : ${result.success ? chalk11.green("\u2713 success") : chalk11.red("\u2717 failed")}`));
|
|
5298
|
+
if (result.files && result.files.length > 0) {
|
|
5299
|
+
console.log(chalk11.gray(` Files (${result.files.length}):`));
|
|
5300
|
+
result.files.slice(0, 8).forEach((f) => {
|
|
5301
|
+
console.log(chalk11.gray(` \u2022 ${f.path}`));
|
|
5302
|
+
});
|
|
5303
|
+
if (result.files.length > 8) {
|
|
5304
|
+
console.log(chalk11.gray(` ... and ${result.files.length - 8} more`));
|
|
5305
|
+
}
|
|
5306
|
+
}
|
|
5307
|
+
console.log("");
|
|
5308
|
+
console.log(chalk11.yellow(" \u25B8 Press Enter to continue"));
|
|
5309
|
+
console.log(chalk11.yellow(' \u25B8 Type "skip" to disable breakpoints for this run'));
|
|
5310
|
+
console.log(chalk11.yellow(' \u25B8 Type "abort" to stop the squad'));
|
|
5311
|
+
console.log("");
|
|
5312
|
+
const answer = await this.promptUser(" \u23F8 ");
|
|
5313
|
+
if (answer.trim().toLowerCase() === "skip") {
|
|
5314
|
+
this.config.breakpoints = false;
|
|
5315
|
+
this.log("Breakpoints disabled for the rest of this run.", "info");
|
|
5316
|
+
} else if (answer.trim().toLowerCase() === "abort") {
|
|
5317
|
+
this.log("Pipeline aborted by developer at breakpoint.", "error");
|
|
5318
|
+
throw new Error("Pipeline aborted by developer at breakpoint");
|
|
5319
|
+
}
|
|
5320
|
+
emitAgentEvent2({
|
|
5321
|
+
type: "breakpoint_resume",
|
|
5322
|
+
agentName: task.assignee,
|
|
5323
|
+
role: task.assignee,
|
|
5324
|
+
message: `Breakpoint resumed after task: ${task.title}`
|
|
5325
|
+
});
|
|
5326
|
+
await this.appendTrace("breakpoint", {
|
|
5327
|
+
taskId: task.id,
|
|
5328
|
+
action: answer.trim().toLowerCase() || "continue"
|
|
5329
|
+
});
|
|
5330
|
+
}
|
|
5331
|
+
/**
|
|
5332
|
+
* Prompt the user for a single line of input via stdin.
|
|
5333
|
+
* If stdin is not a TTY (e.g., running in CI), auto-continues.
|
|
5334
|
+
*/
|
|
5335
|
+
promptUser(prompt) {
|
|
5336
|
+
if (!process.stdin.isTTY) {
|
|
5337
|
+
this.log("Non-interactive environment detected \u2014 auto-continuing breakpoint.", "info");
|
|
5338
|
+
return Promise.resolve("");
|
|
5339
|
+
}
|
|
5340
|
+
return new Promise((resolve3) => {
|
|
5341
|
+
const rl = readline.createInterface({
|
|
5342
|
+
input: process.stdin,
|
|
5343
|
+
output: process.stdout
|
|
5344
|
+
});
|
|
5345
|
+
rl.question(prompt, (answer) => {
|
|
5346
|
+
rl.close();
|
|
5347
|
+
resolve3(answer);
|
|
5348
|
+
});
|
|
5349
|
+
});
|
|
5350
|
+
}
|
|
5113
5351
|
/**
|
|
5114
5352
|
* Styled console logging
|
|
5115
5353
|
*/
|
|
@@ -5168,7 +5406,8 @@ Constraints: Build a production SaaS using Next.js App Router, Prisma, Postgres,
|
|
|
5168
5406
|
const config = {
|
|
5169
5407
|
workspaceRoot: options.output || process.cwd(),
|
|
5170
5408
|
verbose: options.verbose ?? true,
|
|
5171
|
-
autoDeploy: options.deploy ?? false
|
|
5409
|
+
autoDeploy: options.deploy ?? false,
|
|
5410
|
+
breakpoints: options.breakpoints ?? false
|
|
5172
5411
|
};
|
|
5173
5412
|
const spinner = ora4("Assembling the squad...").start();
|
|
5174
5413
|
const orchestrator = new SquadOrchestrator(llm, config);
|
|
@@ -5236,8 +5475,8 @@ function parseGitHubUrl(url) {
|
|
|
5236
5475
|
if (!cleanUrl.startsWith("github.com/")) {
|
|
5237
5476
|
throw new Error("Invalid GitHub URL. Please use format: https://github.com/owner/repo");
|
|
5238
5477
|
}
|
|
5239
|
-
const
|
|
5240
|
-
const parts =
|
|
5478
|
+
const path20 = cleanUrl.replace("github.com/", "");
|
|
5479
|
+
const parts = path20.split("/");
|
|
5241
5480
|
if (parts.length < 2) {
|
|
5242
5481
|
throw new Error("Invalid GitHub URL. Could not extract owner/repo.");
|
|
5243
5482
|
}
|
|
@@ -5645,115 +5884,1393 @@ async function runReplayCommand(runId, llm, options = {}) {
|
|
|
5645
5884
|
}
|
|
5646
5885
|
}
|
|
5647
5886
|
|
|
5648
|
-
// src/commands/
|
|
5887
|
+
// src/commands/audit.ts
|
|
5649
5888
|
import chalk16 from "chalk";
|
|
5650
5889
|
import ora6 from "ora";
|
|
5651
|
-
import
|
|
5652
|
-
import
|
|
5653
|
-
var
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
var EXCLUDED_PATH_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next"]);
|
|
5696
|
-
var EXCLUDED_ALL_PATH_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", ".git", ".next"]);
|
|
5697
|
-
function shouldIncludePath(filePath) {
|
|
5698
|
-
const normalized = filePath.replace(/\\/g, "/").toLowerCase();
|
|
5699
|
-
const segments = normalized.split("/");
|
|
5700
|
-
return !segments.some((segment) => EXCLUDED_PATH_SEGMENTS.has(segment));
|
|
5890
|
+
import * as fs10 from "fs/promises";
|
|
5891
|
+
import * as path10 from "path";
|
|
5892
|
+
var RULES = {
|
|
5893
|
+
// React-specific
|
|
5894
|
+
MISSING_ERROR_BOUNDARY: "react/error-boundary",
|
|
5895
|
+
MISSING_KEY_PROP: "react/key-prop",
|
|
5896
|
+
// Accessibility
|
|
5897
|
+
MISSING_ALT_TEXT: "a11y/alt-text",
|
|
5898
|
+
MISSING_ARIA_LABEL: "a11y/aria-label",
|
|
5899
|
+
MISSING_FORM_LABEL: "a11y/form-label",
|
|
5900
|
+
// Security
|
|
5901
|
+
HARDCODED_SECRET: "security/hardcoded-secret",
|
|
5902
|
+
CONSOLE_LOG_IN_PROD: "security/console-log",
|
|
5903
|
+
// SEO
|
|
5904
|
+
MISSING_META_DESCRIPTION: "seo/meta-description",
|
|
5905
|
+
MISSING_TITLE_TAG: "seo/title-tag",
|
|
5906
|
+
// Quality
|
|
5907
|
+
TODO_IN_CODE: "quality/todo",
|
|
5908
|
+
EMPTY_CATCH: "quality/empty-catch",
|
|
5909
|
+
NO_TESTS: "quality/no-tests"
|
|
5910
|
+
};
|
|
5911
|
+
async function scanDirectory(dir) {
|
|
5912
|
+
const results = [];
|
|
5913
|
+
const ignore = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", "runs", "coverage"]);
|
|
5914
|
+
const extensions = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".html", ".css"]);
|
|
5915
|
+
const walk = async (current) => {
|
|
5916
|
+
let entries;
|
|
5917
|
+
try {
|
|
5918
|
+
entries = await fs10.readdir(current, { withFileTypes: true });
|
|
5919
|
+
} catch {
|
|
5920
|
+
return;
|
|
5921
|
+
}
|
|
5922
|
+
for (const entry of entries) {
|
|
5923
|
+
if (ignore.has(entry.name)) continue;
|
|
5924
|
+
const fullPath = path10.join(current, entry.name);
|
|
5925
|
+
if (entry.isDirectory()) {
|
|
5926
|
+
await walk(fullPath);
|
|
5927
|
+
} else if (entry.isFile() && extensions.has(path10.extname(entry.name))) {
|
|
5928
|
+
results.push(fullPath);
|
|
5929
|
+
}
|
|
5930
|
+
}
|
|
5931
|
+
};
|
|
5932
|
+
await walk(dir);
|
|
5933
|
+
return results;
|
|
5701
5934
|
}
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5935
|
+
function analyzeFile(filePath, content, strict) {
|
|
5936
|
+
const issues = [];
|
|
5937
|
+
const lines = content.split("\n");
|
|
5938
|
+
const ext = path10.extname(filePath);
|
|
5939
|
+
const isReact = ext === ".tsx" || ext === ".jsx";
|
|
5940
|
+
const isHTML = ext === ".html";
|
|
5941
|
+
const relPath = filePath;
|
|
5942
|
+
if (isReact) {
|
|
5943
|
+
if (strict && content.includes("export default") && !content.includes("ErrorBoundary") && !content.includes("error-boundary")) {
|
|
5944
|
+
issues.push({
|
|
5945
|
+
file: relPath,
|
|
5946
|
+
severity: "warning",
|
|
5947
|
+
rule: RULES.MISSING_ERROR_BOUNDARY,
|
|
5948
|
+
message: "Component lacks an ErrorBoundary wrapper. Unhandled errors will crash the entire app."
|
|
5949
|
+
});
|
|
5708
5950
|
}
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5951
|
+
lines.forEach((line, idx) => {
|
|
5952
|
+
if (line.includes(".map(") && !content.includes("key={") && !content.includes("key=")) {
|
|
5953
|
+
issues.push({
|
|
5954
|
+
file: relPath,
|
|
5955
|
+
line: idx + 1,
|
|
5956
|
+
severity: "warning",
|
|
5957
|
+
rule: RULES.MISSING_KEY_PROP,
|
|
5958
|
+
message: "Array .map() rendered in JSX may be missing a `key` prop."
|
|
5959
|
+
});
|
|
5960
|
+
}
|
|
5713
5961
|
});
|
|
5714
5962
|
}
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5963
|
+
if (isReact || isHTML) {
|
|
5964
|
+
lines.forEach((line, idx) => {
|
|
5965
|
+
if (/<img\s/.test(line) && !/alt\s*=/.test(line)) {
|
|
5966
|
+
issues.push({
|
|
5967
|
+
file: relPath,
|
|
5968
|
+
line: idx + 1,
|
|
5969
|
+
severity: strict ? "error" : "warning",
|
|
5970
|
+
rule: RULES.MISSING_ALT_TEXT,
|
|
5971
|
+
message: "<img> element missing `alt` attribute (WCAG 1.1.1)."
|
|
5972
|
+
});
|
|
5973
|
+
}
|
|
5974
|
+
if (/<button\s/.test(line) && !/aria-label/.test(line) && />(\s*)<\/button>/.test(line)) {
|
|
5975
|
+
issues.push({
|
|
5976
|
+
file: relPath,
|
|
5977
|
+
line: idx + 1,
|
|
5978
|
+
severity: "warning",
|
|
5979
|
+
rule: RULES.MISSING_ARIA_LABEL,
|
|
5980
|
+
message: "<button> with no visible text needs `aria-label` (WCAG 4.1.2)."
|
|
5981
|
+
});
|
|
5982
|
+
}
|
|
5983
|
+
if (/<input\s/.test(line) && !/aria-label/.test(line) && !/id\s*=/.test(line)) {
|
|
5984
|
+
issues.push({
|
|
5985
|
+
file: relPath,
|
|
5986
|
+
line: idx + 1,
|
|
5987
|
+
severity: strict ? "error" : "warning",
|
|
5988
|
+
rule: RULES.MISSING_FORM_LABEL,
|
|
5989
|
+
message: "<input> missing `aria-label` or associated <label> (WCAG 1.3.1)."
|
|
5990
|
+
});
|
|
5991
|
+
}
|
|
5992
|
+
});
|
|
5993
|
+
}
|
|
5994
|
+
lines.forEach((line, idx) => {
|
|
5995
|
+
const secretPatterns = [
|
|
5996
|
+
/(?:api[_-]?key|secret|password|token)\s*[:=]\s*['"][A-Za-z0-9_-]{20,}['"]/i,
|
|
5997
|
+
/sk-[a-zA-Z0-9]{20,}/,
|
|
5998
|
+
/ghp_[a-zA-Z0-9]{20,}/
|
|
5999
|
+
];
|
|
6000
|
+
for (const pattern of secretPatterns) {
|
|
6001
|
+
if (pattern.test(line)) {
|
|
6002
|
+
issues.push({
|
|
6003
|
+
file: relPath,
|
|
6004
|
+
line: idx + 1,
|
|
6005
|
+
severity: "error",
|
|
6006
|
+
rule: RULES.HARDCODED_SECRET,
|
|
6007
|
+
message: "Possible hardcoded secret detected. Use environment variables instead."
|
|
6008
|
+
});
|
|
6009
|
+
break;
|
|
6010
|
+
}
|
|
6011
|
+
}
|
|
6012
|
+
if (strict && /console\.(log|debug|info)\s*\(/.test(line) && !filePath.includes("test") && !filePath.includes("spec")) {
|
|
6013
|
+
issues.push({
|
|
6014
|
+
file: relPath,
|
|
6015
|
+
line: idx + 1,
|
|
6016
|
+
severity: "info",
|
|
6017
|
+
rule: RULES.CONSOLE_LOG_IN_PROD,
|
|
6018
|
+
message: "console.log found in production code. Consider removing or using a logger."
|
|
6019
|
+
});
|
|
6020
|
+
}
|
|
6021
|
+
});
|
|
6022
|
+
lines.forEach((line, idx) => {
|
|
6023
|
+
if (/\/\/\s*(TODO|FIXME|HACK|XXX)/i.test(line)) {
|
|
6024
|
+
issues.push({
|
|
6025
|
+
file: relPath,
|
|
6026
|
+
line: idx + 1,
|
|
6027
|
+
severity: "info",
|
|
6028
|
+
rule: RULES.TODO_IN_CODE,
|
|
6029
|
+
message: `Unresolved ${line.match(/(TODO|FIXME|HACK|XXX)/i)?.[0]} comment.`
|
|
6030
|
+
});
|
|
6031
|
+
}
|
|
6032
|
+
if (/catch\s*\([^)]*\)\s*\{\s*\}/.test(line)) {
|
|
6033
|
+
issues.push({
|
|
6034
|
+
file: relPath,
|
|
6035
|
+
line: idx + 1,
|
|
6036
|
+
severity: "warning",
|
|
6037
|
+
rule: RULES.EMPTY_CATCH,
|
|
6038
|
+
message: "Empty catch block swallows errors. At minimum, log the error."
|
|
6039
|
+
});
|
|
6040
|
+
}
|
|
6041
|
+
});
|
|
6042
|
+
if (isHTML) {
|
|
6043
|
+
if (!content.includes("<title")) {
|
|
6044
|
+
issues.push({
|
|
6045
|
+
file: relPath,
|
|
6046
|
+
severity: "warning",
|
|
6047
|
+
rule: RULES.MISSING_TITLE_TAG,
|
|
6048
|
+
message: "HTML page missing <title> tag."
|
|
6049
|
+
});
|
|
6050
|
+
}
|
|
6051
|
+
if (!content.includes("meta") || !content.includes("description")) {
|
|
6052
|
+
issues.push({
|
|
6053
|
+
file: relPath,
|
|
6054
|
+
severity: strict ? "error" : "warning",
|
|
6055
|
+
rule: RULES.MISSING_META_DESCRIPTION,
|
|
6056
|
+
message: 'HTML page missing <meta name="description"> tag.'
|
|
6057
|
+
});
|
|
5720
6058
|
}
|
|
5721
|
-
await fsExtra.ensureDir(join6(targetPath, ".."));
|
|
5722
|
-
await fsExtra.copy(sourcePath, targetPath, { overwrite: true });
|
|
5723
6059
|
}
|
|
6060
|
+
return issues;
|
|
5724
6061
|
}
|
|
5725
|
-
function
|
|
5726
|
-
const
|
|
5727
|
-
const
|
|
5728
|
-
|
|
5729
|
-
}
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
6062
|
+
async function runAuditCommand(options = {}) {
|
|
6063
|
+
const cwd = options.output || process.cwd();
|
|
6064
|
+
const strict = options.prod ?? false;
|
|
6065
|
+
console.log(chalk16.cyan.bold(`
|
|
6066
|
+
\u{1F50D} Agdi Audit${strict ? " \u2014 Production Mode" : ""}
|
|
6067
|
+
`));
|
|
6068
|
+
console.log(chalk16.gray(` Scanning: ${cwd}`));
|
|
6069
|
+
console.log(chalk16.gray(` Mode: ${strict ? chalk16.yellow("STRICT (--prod)") : "Standard"}
|
|
6070
|
+
`));
|
|
6071
|
+
const spinner = ora6("Scanning files...").start();
|
|
6072
|
+
const files = await scanDirectory(cwd);
|
|
6073
|
+
spinner.text = `Analyzing ${files.length} files...`;
|
|
6074
|
+
const allIssues = [];
|
|
6075
|
+
for (const file of files) {
|
|
6076
|
+
try {
|
|
6077
|
+
const content = await fs10.readFile(file, "utf-8");
|
|
6078
|
+
const relPath = path10.relative(cwd, file);
|
|
6079
|
+
const issues = analyzeFile(relPath, content, strict);
|
|
6080
|
+
allIssues.push(...issues);
|
|
6081
|
+
} catch {
|
|
5736
6082
|
}
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
6083
|
+
}
|
|
6084
|
+
const srcFiles = files.filter((f) => !f.includes("test") && !f.includes("spec") && !f.includes("__tests__"));
|
|
6085
|
+
const testFiles = files.filter((f) => f.includes("test") || f.includes("spec") || f.includes("__tests__"));
|
|
6086
|
+
if (strict && testFiles.length === 0 && srcFiles.length > 5) {
|
|
6087
|
+
allIssues.push({
|
|
6088
|
+
file: "(project)",
|
|
6089
|
+
severity: "error",
|
|
6090
|
+
rule: RULES.NO_TESTS,
|
|
6091
|
+
message: `No test files found. Production apps must have tests.`
|
|
5741
6092
|
});
|
|
5742
6093
|
}
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
6094
|
+
const errors = allIssues.filter((i) => i.severity === "error").length;
|
|
6095
|
+
const warnings = allIssues.filter((i) => i.severity === "warning").length;
|
|
6096
|
+
const info = allIssues.filter((i) => i.severity === "info").length;
|
|
6097
|
+
const passed = errors === 0;
|
|
6098
|
+
spinner.stop();
|
|
6099
|
+
if (allIssues.length === 0) {
|
|
6100
|
+
console.log(chalk16.green.bold(" \u2705 No issues found! Your code looks production-ready.\n"));
|
|
6101
|
+
} else {
|
|
6102
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
6103
|
+
for (const issue of allIssues) {
|
|
6104
|
+
const key = issue.severity;
|
|
6105
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
6106
|
+
grouped.get(key).push(issue);
|
|
6107
|
+
}
|
|
6108
|
+
for (const severity of ["error", "warning", "info"]) {
|
|
6109
|
+
const items = grouped.get(severity);
|
|
6110
|
+
if (!items || items.length === 0) continue;
|
|
6111
|
+
const icon = severity === "error" ? "\u274C" : severity === "warning" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
|
|
6112
|
+
const color = severity === "error" ? chalk16.red : severity === "warning" ? chalk16.yellow : chalk16.gray;
|
|
6113
|
+
console.log(color.bold(`
|
|
6114
|
+
${icon} ${severity.toUpperCase()} (${items.length})
|
|
6115
|
+
`));
|
|
6116
|
+
items.slice(0, 15).forEach((issue) => {
|
|
6117
|
+
const loc = issue.line ? `:${issue.line}` : "";
|
|
6118
|
+
console.log(color(` ${issue.file}${loc}`));
|
|
6119
|
+
console.log(chalk16.gray(` [${issue.rule}] ${issue.message}`));
|
|
6120
|
+
});
|
|
6121
|
+
if (items.length > 15) {
|
|
6122
|
+
console.log(color(` ... and ${items.length - 15} more`));
|
|
6123
|
+
}
|
|
5748
6124
|
}
|
|
5749
|
-
await fsExtra.ensureDir(join6(targetPath, ".."));
|
|
5750
|
-
await fsExtra.copy(sourcePath, targetPath, { overwrite: true });
|
|
5751
6125
|
}
|
|
6126
|
+
console.log("");
|
|
6127
|
+
console.log(chalk16.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6128
|
+
console.log(chalk16.gray(` Files scanned : ${files.length}`));
|
|
6129
|
+
console.log(chalk16.red(` Errors : ${errors}`));
|
|
6130
|
+
console.log(chalk16.yellow(` Warnings : ${warnings}`));
|
|
6131
|
+
console.log(chalk16.gray(` Info : ${info}`));
|
|
6132
|
+
console.log(chalk16.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6133
|
+
console.log(chalk16.gray(` Verdict : ${passed ? chalk16.green.bold("PASS \u2713") : chalk16.red.bold("FAIL \u2717")}`));
|
|
6134
|
+
console.log("");
|
|
6135
|
+
if (!passed && strict) {
|
|
6136
|
+
console.log(chalk16.yellow(" \u{1F4A1} Run `agdi audit --fix` to auto-fix issues with the QA agent.\n"));
|
|
6137
|
+
}
|
|
6138
|
+
return {
|
|
6139
|
+
issues: allIssues,
|
|
6140
|
+
passed,
|
|
6141
|
+
summary: {
|
|
6142
|
+
errors,
|
|
6143
|
+
warnings,
|
|
6144
|
+
info,
|
|
6145
|
+
filesScanned: files.length
|
|
6146
|
+
}
|
|
6147
|
+
};
|
|
5752
6148
|
}
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
6149
|
+
|
|
6150
|
+
// src/commands/api-sandbox.ts
|
|
6151
|
+
import chalk17 from "chalk";
|
|
6152
|
+
import ora7 from "ora";
|
|
6153
|
+
var RATE_LIMIT_DEFAULTS = {
|
|
6154
|
+
/** Delay between consecutive endpoint tests (ms) */
|
|
6155
|
+
delayMs: 500,
|
|
6156
|
+
/** Maximum concurrent requests */
|
|
6157
|
+
maxConcurrent: 1,
|
|
6158
|
+
/** Timeout per request (ms) */
|
|
6159
|
+
timeoutMs: 5e3,
|
|
6160
|
+
/** Max endpoints to test in a single run */
|
|
6161
|
+
maxTestEndpoints: 5
|
|
6162
|
+
};
|
|
6163
|
+
function sleep(ms) {
|
|
6164
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
6165
|
+
}
|
|
6166
|
+
async function fetchAndParse(url) {
|
|
6167
|
+
const response = await fetch(url, {
|
|
6168
|
+
headers: {
|
|
6169
|
+
"User-Agent": "Agdi-Sandbox/1.0",
|
|
6170
|
+
Accept: "text/html, application/json, text/plain"
|
|
6171
|
+
}
|
|
6172
|
+
});
|
|
6173
|
+
if (!response.ok) {
|
|
6174
|
+
throw new Error(`Failed to fetch ${url}: HTTP ${response.status}`);
|
|
6175
|
+
}
|
|
6176
|
+
const contentType = response.headers.get("content-type") || "";
|
|
6177
|
+
const body = await response.text();
|
|
6178
|
+
if (contentType.includes("json") || body.trim().startsWith("{")) {
|
|
6179
|
+
return { title: "OpenAPI Spec", body };
|
|
6180
|
+
}
|
|
6181
|
+
const stripped = body.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<nav[\s\S]*?<\/nav>/gi, "").replace(/<footer[\s\S]*?<\/footer>/gi, "").replace(/<header[\s\S]*?<\/header>/gi, "").replace(/<[^>]+>/g, "\n").replace(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\n{3,}/g, "\n\n").trim();
|
|
6182
|
+
const titleMatch = body.match(/<title[^>]*>([^<]+)<\/title>/i);
|
|
6183
|
+
const title = titleMatch ? titleMatch[1].trim() : url;
|
|
6184
|
+
return { title, body: stripped };
|
|
6185
|
+
}
|
|
6186
|
+
function extractEndpoints(text) {
|
|
6187
|
+
const endpoints = [];
|
|
6188
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6189
|
+
const patterns = [
|
|
6190
|
+
// REST patterns: GET /api/users, POST /v1/items
|
|
6191
|
+
/\b(GET|POST|PUT|DELETE|PATCH)\s+(\/[a-zA-Z0-9_\-/:{}?&=.]+)/gi,
|
|
6192
|
+
// curl examples
|
|
6193
|
+
/curl\s+.*?(GET|POST|PUT|DELETE|PATCH)\s+['"]?(https?:\/\/[^\s'"]+)/gi
|
|
6194
|
+
];
|
|
6195
|
+
for (const pattern of patterns) {
|
|
6196
|
+
let match;
|
|
6197
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
6198
|
+
const method = match[1].toUpperCase();
|
|
6199
|
+
const path20 = match[2];
|
|
6200
|
+
const key = `${method} ${path20}`;
|
|
6201
|
+
if (!seen.has(key) && path20.length > 1) {
|
|
6202
|
+
seen.add(key);
|
|
6203
|
+
const idx = text.indexOf(match[0]);
|
|
6204
|
+
const context = text.substring(Math.max(0, idx - 200), Math.min(text.length, idx + 200));
|
|
6205
|
+
const descLine = context.split("\n").find((l) => l.length > 20 && !l.includes(method)) || "";
|
|
6206
|
+
endpoints.push({
|
|
6207
|
+
method,
|
|
6208
|
+
path: path20,
|
|
6209
|
+
description: descLine.trim().substring(0, 120)
|
|
6210
|
+
});
|
|
6211
|
+
}
|
|
6212
|
+
}
|
|
6213
|
+
}
|
|
6214
|
+
try {
|
|
6215
|
+
const json = JSON.parse(text);
|
|
6216
|
+
if (json.paths) {
|
|
6217
|
+
for (const [apiPath, methods] of Object.entries(json.paths)) {
|
|
6218
|
+
for (const [method, spec] of Object.entries(methods)) {
|
|
6219
|
+
if (["get", "post", "put", "delete", "patch"].includes(method)) {
|
|
6220
|
+
const key = `${method.toUpperCase()} ${apiPath}`;
|
|
6221
|
+
if (!seen.has(key)) {
|
|
6222
|
+
seen.add(key);
|
|
6223
|
+
endpoints.push({
|
|
6224
|
+
method: method.toUpperCase(),
|
|
6225
|
+
path: apiPath,
|
|
6226
|
+
description: spec.summary || spec.description || "",
|
|
6227
|
+
parameters: spec.parameters?.map((p) => p.name) || []
|
|
6228
|
+
});
|
|
6229
|
+
}
|
|
6230
|
+
}
|
|
6231
|
+
}
|
|
6232
|
+
}
|
|
6233
|
+
}
|
|
6234
|
+
} catch {
|
|
6235
|
+
}
|
|
6236
|
+
return endpoints;
|
|
6237
|
+
}
|
|
6238
|
+
async function testEndpoint(baseUrl, endpoint, apiKey) {
|
|
6239
|
+
const url = `${baseUrl}${endpoint.path}`.replace(/{[^}]+}/g, "test");
|
|
6240
|
+
const headers = {
|
|
6241
|
+
Accept: "application/json",
|
|
6242
|
+
"User-Agent": "Agdi-Sandbox/1.0"
|
|
6243
|
+
};
|
|
6244
|
+
if (apiKey) {
|
|
6245
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
6246
|
+
}
|
|
6247
|
+
try {
|
|
6248
|
+
const response = await fetch(url, {
|
|
6249
|
+
method: endpoint.method === "GET" ? "GET" : "HEAD",
|
|
6250
|
+
headers,
|
|
6251
|
+
signal: AbortSignal.timeout(RATE_LIMIT_DEFAULTS.timeoutMs)
|
|
6252
|
+
});
|
|
6253
|
+
let shape = `HTTP ${response.status}`;
|
|
6254
|
+
if (response.ok) {
|
|
6255
|
+
try {
|
|
6256
|
+
const json = await response.json();
|
|
6257
|
+
shape = describeShape(json);
|
|
6258
|
+
} catch {
|
|
6259
|
+
shape = `HTTP ${response.status} (non-JSON)`;
|
|
6260
|
+
}
|
|
6261
|
+
}
|
|
6262
|
+
return { status: response.status, shape };
|
|
6263
|
+
} catch {
|
|
6264
|
+
return { status: 0, shape: "unreachable" };
|
|
6265
|
+
}
|
|
6266
|
+
}
|
|
6267
|
+
function describeShape(obj, depth = 0) {
|
|
6268
|
+
if (depth > 2) return "...";
|
|
6269
|
+
if (obj === null) return "null";
|
|
6270
|
+
if (Array.isArray(obj)) {
|
|
6271
|
+
if (obj.length === 0) return "[]";
|
|
6272
|
+
return `[${describeShape(obj[0], depth + 1)}]`;
|
|
6273
|
+
}
|
|
6274
|
+
if (typeof obj === "object") {
|
|
6275
|
+
const keys = Object.keys(obj).slice(0, 6);
|
|
6276
|
+
const fields = keys.map((k) => `${k}: ${typeof obj[k]}`).join(", ");
|
|
6277
|
+
const extra = Object.keys(obj).length > 6 ? ", ..." : "";
|
|
6278
|
+
return `{ ${fields}${extra} }`;
|
|
6279
|
+
}
|
|
6280
|
+
return typeof obj;
|
|
6281
|
+
}
|
|
6282
|
+
async function runSandboxCommand(url, options = {}) {
|
|
6283
|
+
console.log(chalk17.cyan.bold(`
|
|
6284
|
+
\u{1F9EA} Agdi API Sandbox Reader
|
|
6285
|
+
`));
|
|
6286
|
+
console.log(chalk17.gray(` Target: ${url}`));
|
|
6287
|
+
console.log(chalk17.gray(` Test: ${options.test ? chalk17.yellow("YES") : "no"}
|
|
6288
|
+
`));
|
|
6289
|
+
const spinner = ora7("Fetching API documentation...").start();
|
|
6290
|
+
const { title, body } = await fetchAndParse(url);
|
|
6291
|
+
spinner.text = "Extracting endpoints...";
|
|
6292
|
+
const endpoints = extractEndpoints(body);
|
|
6293
|
+
spinner.stop();
|
|
6294
|
+
console.log(chalk17.green(` \u2713 Found ${endpoints.length} endpoint(s) from "${title}"
|
|
6295
|
+
`));
|
|
6296
|
+
if (endpoints.length > 0) {
|
|
6297
|
+
console.log(chalk17.cyan.bold(" Discovered Endpoints:\n"));
|
|
6298
|
+
endpoints.slice(0, 20).forEach((ep, i) => {
|
|
6299
|
+
const methodColor = ep.method === "GET" ? chalk17.green : ep.method === "POST" ? chalk17.yellow : ep.method === "DELETE" ? chalk17.red : chalk17.blue;
|
|
6300
|
+
console.log(` ${chalk17.gray(`${i + 1}.`)} ${methodColor(ep.method.padEnd(7))} ${chalk17.white(ep.path)}`);
|
|
6301
|
+
if (ep.description) {
|
|
6302
|
+
console.log(chalk17.gray(` ${ep.description}`));
|
|
6303
|
+
}
|
|
6304
|
+
});
|
|
6305
|
+
if (endpoints.length > 20) {
|
|
6306
|
+
console.log(chalk17.gray(` ... and ${endpoints.length - 20} more`));
|
|
6307
|
+
}
|
|
6308
|
+
} else {
|
|
6309
|
+
console.log(chalk17.yellow(" \u26A0 No API endpoints detected. The page may not be API documentation."));
|
|
6310
|
+
}
|
|
6311
|
+
let testResults;
|
|
6312
|
+
if (options.test && endpoints.length > 0) {
|
|
6313
|
+
console.log(chalk17.cyan.bold("\n Live Testing:\n"));
|
|
6314
|
+
const baseUrl = new URL(url).origin;
|
|
6315
|
+
testResults = [];
|
|
6316
|
+
for (const ep of endpoints.slice(0, RATE_LIMIT_DEFAULTS.maxTestEndpoints)) {
|
|
6317
|
+
if (testResults.length > 0) {
|
|
6318
|
+
await sleep(RATE_LIMIT_DEFAULTS.delayMs);
|
|
6319
|
+
}
|
|
6320
|
+
const result = await testEndpoint(baseUrl, ep, options.apiKey);
|
|
6321
|
+
testResults.push({
|
|
6322
|
+
endpoint: `${ep.method} ${ep.path}`,
|
|
6323
|
+
status: result.status,
|
|
6324
|
+
shape: result.shape
|
|
6325
|
+
});
|
|
6326
|
+
const statusColor = result.status >= 200 && result.status < 300 ? chalk17.green : result.status === 0 ? chalk17.red : chalk17.yellow;
|
|
6327
|
+
console.log(` ${statusColor(`${result.status}`)} ${ep.method} ${ep.path}`);
|
|
6328
|
+
console.log(chalk17.gray(` Shape: ${result.shape}`));
|
|
6329
|
+
}
|
|
6330
|
+
}
|
|
6331
|
+
const rawMarkdown = [
|
|
6332
|
+
`# API Documentation: ${title}`,
|
|
6333
|
+
`Source: ${url}`,
|
|
6334
|
+
"",
|
|
6335
|
+
"## Endpoints",
|
|
6336
|
+
...endpoints.map((ep) => `- **${ep.method}** \`${ep.path}\` \u2014 ${ep.description || "No description"}`),
|
|
6337
|
+
...testResults ? [
|
|
6338
|
+
"",
|
|
6339
|
+
"## Live Test Results",
|
|
6340
|
+
...testResults.map((r) => `- \`${r.endpoint}\` \u2192 ${r.status} \u2014 Shape: \`${r.shape}\``)
|
|
6341
|
+
] : []
|
|
6342
|
+
].join("\n");
|
|
6343
|
+
console.log("");
|
|
6344
|
+
console.log(chalk17.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6345
|
+
console.log(chalk17.gray(` Title : ${title}`));
|
|
6346
|
+
console.log(chalk17.gray(` Endpoints : ${endpoints.length}`));
|
|
6347
|
+
console.log(chalk17.gray(` Tested : ${testResults?.length || 0}`));
|
|
6348
|
+
console.log(chalk17.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6349
|
+
console.log("");
|
|
6350
|
+
if (options.inject) {
|
|
6351
|
+
console.log(chalk17.green(" \u{1F489} Endpoint summary ready for Squad injection.\n"));
|
|
6352
|
+
}
|
|
6353
|
+
return {
|
|
6354
|
+
url,
|
|
6355
|
+
title,
|
|
6356
|
+
endpoints,
|
|
6357
|
+
rawMarkdown,
|
|
6358
|
+
testResults
|
|
6359
|
+
};
|
|
6360
|
+
}
|
|
6361
|
+
|
|
6362
|
+
// src/commands/visual-test.ts
|
|
6363
|
+
import chalk18 from "chalk";
|
|
6364
|
+
import ora8 from "ora";
|
|
6365
|
+
import * as fs11 from "fs/promises";
|
|
6366
|
+
import * as path11 from "path";
|
|
6367
|
+
import { exec as execCb2 } from "child_process";
|
|
6368
|
+
import { promisify as promisify4 } from "util";
|
|
6369
|
+
import crypto3 from "crypto";
|
|
6370
|
+
var execAsync3 = promisify4(execCb2);
|
|
6371
|
+
async function discoverRoutes(cwd) {
|
|
6372
|
+
const routes = ["/"];
|
|
6373
|
+
const routePatterns = [
|
|
6374
|
+
// Next.js app router
|
|
6375
|
+
{ glob: "src/app/**/page.tsx", transform: (p) => "/" + p.replace("src/app/", "").replace("/page.tsx", "").replace("page.tsx", "") },
|
|
6376
|
+
{ glob: "app/**/page.tsx", transform: (p) => "/" + p.replace("app/", "").replace("/page.tsx", "").replace("page.tsx", "") },
|
|
6377
|
+
// Next.js pages router
|
|
6378
|
+
{ glob: "src/pages/**/*.tsx", transform: (p) => "/" + p.replace("src/pages/", "").replace(".tsx", "").replace("/index", "").replace("index", "") },
|
|
6379
|
+
{ glob: "pages/**/*.tsx", transform: (p) => "/" + p.replace("pages/", "").replace(".tsx", "").replace("/index", "").replace("index", "") }
|
|
6380
|
+
];
|
|
6381
|
+
for (const pattern of routePatterns) {
|
|
6382
|
+
try {
|
|
6383
|
+
const { stdout } = await execAsync3(`npx -y glob "${pattern.glob}" --cwd "${cwd}" 2>nul`, { cwd });
|
|
6384
|
+
const files = stdout.trim().split("\n").filter(Boolean);
|
|
6385
|
+
for (const file of files) {
|
|
6386
|
+
const route = pattern.transform(file).replace(/\\/g, "/");
|
|
6387
|
+
if (route && !route.includes("_app") && !route.includes("_document") && !route.includes("api/")) {
|
|
6388
|
+
routes.push(route === "/" ? route : route.replace(/\/$/, ""));
|
|
6389
|
+
}
|
|
6390
|
+
}
|
|
6391
|
+
} catch {
|
|
6392
|
+
}
|
|
6393
|
+
}
|
|
6394
|
+
return [...new Set(routes)];
|
|
6395
|
+
}
|
|
6396
|
+
async function hashFile(filePath) {
|
|
6397
|
+
try {
|
|
6398
|
+
const content = await fs11.readFile(filePath);
|
|
6399
|
+
return crypto3.createHash("md5").update(content).digest("hex");
|
|
6400
|
+
} catch {
|
|
6401
|
+
return "";
|
|
6402
|
+
}
|
|
6403
|
+
}
|
|
6404
|
+
async function takeScreenshot(url, outputPath) {
|
|
6405
|
+
try {
|
|
6406
|
+
await execAsync3(
|
|
6407
|
+
`npx -y playwright screenshot --browser chromium --full-page "${url}" "${outputPath}"`,
|
|
6408
|
+
{ timeout: 3e4 }
|
|
6409
|
+
);
|
|
6410
|
+
return true;
|
|
6411
|
+
} catch {
|
|
6412
|
+
}
|
|
6413
|
+
try {
|
|
6414
|
+
const script = `
|
|
6415
|
+
const puppeteer = require('puppeteer');
|
|
6416
|
+
(async () => {
|
|
6417
|
+
const browser = await puppeteer.launch({ headless: 'new' });
|
|
6418
|
+
const page = await browser.newPage();
|
|
6419
|
+
await page.setViewport({ width: 1280, height: 720 });
|
|
6420
|
+
await page.goto('${url}', { waitUntil: 'networkidle0', timeout: 15000 });
|
|
6421
|
+
await page.screenshot({ path: '${outputPath.replace(/\\/g, "\\\\")}', fullPage: true });
|
|
6422
|
+
await browser.close();
|
|
6423
|
+
})();
|
|
6424
|
+
`;
|
|
6425
|
+
await execAsync3(`node -e "${script.replace(/"/g, '\\"').replace(/\n/g, " ")}"`, { timeout: 3e4 });
|
|
6426
|
+
return true;
|
|
6427
|
+
} catch {
|
|
6428
|
+
}
|
|
6429
|
+
return false;
|
|
6430
|
+
}
|
|
6431
|
+
async function runVisualTestCommand(options = {}) {
|
|
6432
|
+
const cwd = options.output || process.cwd();
|
|
6433
|
+
const port = options.port || 3e3;
|
|
6434
|
+
const baseUrl = `http://localhost:${port}`;
|
|
6435
|
+
const baselineDir = path11.join(cwd, ".agdi", "visual-baselines");
|
|
6436
|
+
const currentDir = path11.join(cwd, ".agdi", "visual-current");
|
|
6437
|
+
console.log(chalk18.cyan.bold("\n\u{1F4F8} Agdi Visual Regression Testing\n"));
|
|
6438
|
+
console.log(chalk18.gray(` Base URL : ${baseUrl}`));
|
|
6439
|
+
console.log(chalk18.gray(` Baselines: ${path11.relative(cwd, baselineDir)}`));
|
|
6440
|
+
console.log(chalk18.gray(` Mode : ${options.update ? chalk18.yellow("UPDATE BASELINES") : "Compare"}
|
|
6441
|
+
`));
|
|
6442
|
+
await fs11.mkdir(baselineDir, { recursive: true });
|
|
6443
|
+
await fs11.mkdir(currentDir, { recursive: true });
|
|
6444
|
+
const spinner = ora8("Discovering routes...").start();
|
|
6445
|
+
let routes;
|
|
6446
|
+
if (options.routes) {
|
|
6447
|
+
routes = options.routes.split(",").map((r) => r.trim());
|
|
6448
|
+
} else {
|
|
6449
|
+
routes = await discoverRoutes(cwd);
|
|
6450
|
+
}
|
|
6451
|
+
spinner.stop();
|
|
6452
|
+
console.log(chalk18.gray(` Routes : ${routes.join(", ")}
|
|
6453
|
+
`));
|
|
6454
|
+
let serverRunning = false;
|
|
6455
|
+
try {
|
|
6456
|
+
const response = await fetch(baseUrl, { signal: AbortSignal.timeout(3e3) });
|
|
6457
|
+
serverRunning = response.ok || response.status < 500;
|
|
6458
|
+
} catch {
|
|
6459
|
+
}
|
|
6460
|
+
if (!serverRunning) {
|
|
6461
|
+
console.log(chalk18.yellow(" \u26A0 Dev server not detected. Start it with `npm run dev` first."));
|
|
6462
|
+
console.log(chalk18.gray(` Expected at: ${baseUrl}
|
|
6463
|
+
`));
|
|
6464
|
+
return {
|
|
6465
|
+
results: [{ route: "*", status: "error", error: "Dev server not running" }],
|
|
6466
|
+
passed: 0,
|
|
6467
|
+
failed: 0,
|
|
6468
|
+
newBaselines: 0,
|
|
6469
|
+
errors: 1
|
|
6470
|
+
};
|
|
6471
|
+
}
|
|
6472
|
+
const results = [];
|
|
6473
|
+
let canScreenshot = false;
|
|
6474
|
+
try {
|
|
6475
|
+
await execAsync3("npx -y playwright --version", { timeout: 1e4 });
|
|
6476
|
+
canScreenshot = true;
|
|
6477
|
+
} catch {
|
|
6478
|
+
try {
|
|
6479
|
+
await execAsync3(`node -e "require('puppeteer')"`, { timeout: 5e3 });
|
|
6480
|
+
canScreenshot = true;
|
|
6481
|
+
} catch {
|
|
6482
|
+
}
|
|
6483
|
+
}
|
|
6484
|
+
for (const route of routes) {
|
|
6485
|
+
const safeName = route.replace(/\//g, "_").replace(/^_/, "root") || "root";
|
|
6486
|
+
const currentPath = path11.join(currentDir, `${safeName}.png`);
|
|
6487
|
+
const baselinePath = path11.join(baselineDir, `${safeName}.png`);
|
|
6488
|
+
const url = `${baseUrl}${route}`;
|
|
6489
|
+
process.stdout.write(chalk18.gray(` \u{1F4F7} ${route.padEnd(30)}`));
|
|
6490
|
+
if (canScreenshot) {
|
|
6491
|
+
const success = await takeScreenshot(url, currentPath);
|
|
6492
|
+
if (!success) {
|
|
6493
|
+
console.log(chalk18.red("\u2717 Screenshot failed"));
|
|
6494
|
+
results.push({ route, status: "error", error: "Screenshot capture failed" });
|
|
6495
|
+
continue;
|
|
6496
|
+
}
|
|
6497
|
+
} else {
|
|
6498
|
+
try {
|
|
6499
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(5e3) });
|
|
6500
|
+
const html = await response.text();
|
|
6501
|
+
const htmlHash = crypto3.createHash("md5").update(html).digest("hex");
|
|
6502
|
+
await fs11.writeFile(currentPath.replace(".png", ".hash"), htmlHash, "utf-8");
|
|
6503
|
+
try {
|
|
6504
|
+
const baselineHash = await fs11.readFile(baselinePath.replace(".png", ".hash"), "utf-8");
|
|
6505
|
+
if (options.update) {
|
|
6506
|
+
await fs11.writeFile(baselinePath.replace(".png", ".hash"), htmlHash, "utf-8");
|
|
6507
|
+
console.log(chalk18.blue("\u2B06 Updated"));
|
|
6508
|
+
results.push({ route, status: "updated" });
|
|
6509
|
+
} else if (htmlHash === baselineHash) {
|
|
6510
|
+
console.log(chalk18.green("\u2713 Pass"));
|
|
6511
|
+
results.push({ route, status: "pass" });
|
|
6512
|
+
} else {
|
|
6513
|
+
console.log(chalk18.red("\u2717 Changed"));
|
|
6514
|
+
results.push({ route, status: "fail", diffPercentage: 100 });
|
|
6515
|
+
}
|
|
6516
|
+
} catch {
|
|
6517
|
+
await fs11.writeFile(baselinePath.replace(".png", ".hash"), htmlHash, "utf-8");
|
|
6518
|
+
console.log(chalk18.cyan("\u2605 New baseline"));
|
|
6519
|
+
results.push({ route, status: "new", screenshotPath: currentPath });
|
|
6520
|
+
}
|
|
6521
|
+
continue;
|
|
6522
|
+
} catch (err) {
|
|
6523
|
+
console.log(chalk18.red("\u2717 Unreachable"));
|
|
6524
|
+
results.push({ route, status: "error", error: `Route unreachable: ${route}` });
|
|
6525
|
+
continue;
|
|
6526
|
+
}
|
|
6527
|
+
}
|
|
6528
|
+
try {
|
|
6529
|
+
await fs11.access(baselinePath);
|
|
6530
|
+
const currentHash = await hashFile(currentPath);
|
|
6531
|
+
const baselineHash = await hashFile(baselinePath);
|
|
6532
|
+
if (options.update) {
|
|
6533
|
+
await fs11.copyFile(currentPath, baselinePath);
|
|
6534
|
+
console.log(chalk18.blue("\u2B06 Updated"));
|
|
6535
|
+
results.push({ route, status: "updated", screenshotPath: currentPath, baselinePath });
|
|
6536
|
+
} else if (currentHash === baselineHash) {
|
|
6537
|
+
console.log(chalk18.green("\u2713 Pass"));
|
|
6538
|
+
results.push({ route, status: "pass", screenshotPath: currentPath, baselinePath });
|
|
6539
|
+
} else {
|
|
6540
|
+
console.log(chalk18.red("\u2717 Visual diff detected"));
|
|
6541
|
+
results.push({ route, status: "fail", screenshotPath: currentPath, baselinePath, diffPercentage: 100 });
|
|
6542
|
+
}
|
|
6543
|
+
} catch {
|
|
6544
|
+
await fs11.copyFile(currentPath, baselinePath);
|
|
6545
|
+
console.log(chalk18.cyan("\u2605 New baseline"));
|
|
6546
|
+
results.push({ route, status: "new", screenshotPath: currentPath, baselinePath });
|
|
6547
|
+
}
|
|
6548
|
+
}
|
|
6549
|
+
const passed = results.filter((r) => r.status === "pass").length;
|
|
6550
|
+
const failed = results.filter((r) => r.status === "fail").length;
|
|
6551
|
+
const newBaselines = results.filter((r) => r.status === "new" || r.status === "updated").length;
|
|
6552
|
+
const errors = results.filter((r) => r.status === "error").length;
|
|
6553
|
+
console.log("");
|
|
6554
|
+
console.log(chalk18.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6555
|
+
console.log(chalk18.green(` Passed : ${passed}`));
|
|
6556
|
+
console.log(chalk18.red(` Failed : ${failed}`));
|
|
6557
|
+
console.log(chalk18.blue(` New/Updated : ${newBaselines}`));
|
|
6558
|
+
console.log(chalk18.gray(` Errors : ${errors}`));
|
|
6559
|
+
console.log(chalk18.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6560
|
+
console.log(chalk18.gray(` Verdict : ${failed === 0 && errors === 0 ? chalk18.green.bold("PASS \u2713") : chalk18.red.bold("FAIL \u2717")}`));
|
|
6561
|
+
console.log("");
|
|
6562
|
+
if (failed > 0) {
|
|
6563
|
+
console.log(chalk18.yellow(" \u{1F4A1} Run `agdi visual-test --update` to accept current screenshots as new baselines.\n"));
|
|
6564
|
+
}
|
|
6565
|
+
if (!canScreenshot) {
|
|
6566
|
+
console.log(chalk18.gray(" \u2139 Install Playwright for full screenshot support: npx playwright install\n"));
|
|
6567
|
+
}
|
|
6568
|
+
return { results, passed, failed, newBaselines, errors };
|
|
6569
|
+
}
|
|
6570
|
+
|
|
6571
|
+
// src/commands/git-memory.ts
|
|
6572
|
+
import chalk19 from "chalk";
|
|
6573
|
+
import ora9 from "ora";
|
|
6574
|
+
import { exec as execCb3 } from "child_process";
|
|
6575
|
+
import { promisify as promisify5 } from "util";
|
|
6576
|
+
import * as path12 from "path";
|
|
6577
|
+
var execAsync4 = promisify5(execCb3);
|
|
6578
|
+
async function parseGitLog(cwd, depth) {
|
|
6579
|
+
const sep2 = "---AGDI_SEP---";
|
|
6580
|
+
const format = `%H${sep2}%ai${sep2}%an${sep2}%s${sep2}%b${sep2}`;
|
|
6581
|
+
try {
|
|
6582
|
+
const { stdout } = await execAsync4(
|
|
6583
|
+
`git log -${depth} --pretty=format:"${format}" --shortstat`,
|
|
6584
|
+
{ cwd, maxBuffer: 10 * 1024 * 1024 }
|
|
6585
|
+
);
|
|
6586
|
+
const commits = [];
|
|
6587
|
+
const entries = stdout.split(format.replace(/%[A-Za-z]+/g, "")).filter(Boolean);
|
|
6588
|
+
const lines = stdout.split("\n");
|
|
6589
|
+
let current = null;
|
|
6590
|
+
for (const line of lines) {
|
|
6591
|
+
if (line.includes(sep2)) {
|
|
6592
|
+
if (current?.hash) {
|
|
6593
|
+
commits.push(current);
|
|
6594
|
+
}
|
|
6595
|
+
const parts = line.split(sep2);
|
|
6596
|
+
current = {
|
|
6597
|
+
hash: (parts[0] || "").trim().slice(0, 8),
|
|
6598
|
+
date: (parts[1] || "").trim(),
|
|
6599
|
+
author: (parts[2] || "").trim(),
|
|
6600
|
+
subject: (parts[3] || "").trim(),
|
|
6601
|
+
body: (parts[4] || "").trim(),
|
|
6602
|
+
filesChanged: 0
|
|
6603
|
+
};
|
|
6604
|
+
} else if (current && /\d+ file/.test(line)) {
|
|
6605
|
+
const match = line.match(/(\d+) file/);
|
|
6606
|
+
if (match) {
|
|
6607
|
+
current.filesChanged = parseInt(match[1], 10);
|
|
6608
|
+
}
|
|
6609
|
+
}
|
|
6610
|
+
}
|
|
6611
|
+
if (current?.hash) {
|
|
6612
|
+
commits.push(current);
|
|
6613
|
+
}
|
|
6614
|
+
return commits;
|
|
6615
|
+
} catch {
|
|
6616
|
+
return [];
|
|
6617
|
+
}
|
|
6618
|
+
}
|
|
6619
|
+
async function getHotFiles(cwd, depth) {
|
|
6620
|
+
try {
|
|
6621
|
+
const { stdout } = await execAsync4(
|
|
6622
|
+
`git log -${depth} --name-only --pretty=format: | sort | uniq -c | sort -rn`,
|
|
6623
|
+
{ cwd, maxBuffer: 5 * 1024 * 1024 }
|
|
6624
|
+
);
|
|
6625
|
+
const fileCounts = /* @__PURE__ */ new Map();
|
|
6626
|
+
const { stdout: winStdout } = await execAsync4(
|
|
6627
|
+
`git log -${depth} --name-only --pretty=format:`,
|
|
6628
|
+
{ cwd, maxBuffer: 5 * 1024 * 1024 }
|
|
6629
|
+
);
|
|
6630
|
+
winStdout.split("\n").filter(Boolean).forEach((f) => {
|
|
6631
|
+
const file = f.trim();
|
|
6632
|
+
if (file) {
|
|
6633
|
+
fileCounts.set(file, (fileCounts.get(file) || 0) + 1);
|
|
6634
|
+
}
|
|
6635
|
+
});
|
|
6636
|
+
return [...fileCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 15).map(([file, changes]) => ({ file, changes }));
|
|
6637
|
+
} catch {
|
|
6638
|
+
return [];
|
|
6639
|
+
}
|
|
6640
|
+
}
|
|
6641
|
+
async function getContributors(cwd) {
|
|
6642
|
+
try {
|
|
6643
|
+
const { stdout } = await execAsync4(
|
|
6644
|
+
'git log --format="%an" | sort -u',
|
|
6645
|
+
{ cwd }
|
|
6646
|
+
);
|
|
6647
|
+
const { stdout: winStdout } = await execAsync4(
|
|
6648
|
+
'git log --format="%an"',
|
|
6649
|
+
{ cwd }
|
|
6650
|
+
);
|
|
6651
|
+
return [...new Set(winStdout.split("\n").map((s) => s.trim()).filter(Boolean))];
|
|
6652
|
+
} catch {
|
|
6653
|
+
return [];
|
|
6654
|
+
}
|
|
6655
|
+
}
|
|
6656
|
+
function detectPatterns(commits) {
|
|
6657
|
+
const patterns = [];
|
|
6658
|
+
const subjects = commits.map((c) => c.subject.toLowerCase());
|
|
6659
|
+
const conventional = subjects.filter((s) => /^(feat|fix|chore|docs|style|refactor|test|ci)\(/.test(s));
|
|
6660
|
+
if (conventional.length > commits.length * 0.3) {
|
|
6661
|
+
patterns.push("Uses Conventional Commits");
|
|
6662
|
+
}
|
|
6663
|
+
const fixes = subjects.filter((s) => s.includes("fix"));
|
|
6664
|
+
if (fixes.length > commits.length * 0.3) {
|
|
6665
|
+
patterns.push(`High fix ratio (${Math.round(fixes.length / commits.length * 100)}%) \u2014 code may need stabilization`);
|
|
6666
|
+
}
|
|
6667
|
+
const refactors = subjects.filter((s) => s.includes("refactor"));
|
|
6668
|
+
if (refactors.length > 3) {
|
|
6669
|
+
patterns.push("Active refactoring \u2014 check for deprecated patterns");
|
|
6670
|
+
}
|
|
6671
|
+
const features = subjects.filter((s) => s.includes("feat") || s.includes("add"));
|
|
6672
|
+
if (features.length > 5) {
|
|
6673
|
+
patterns.push(`Rapid feature development (${features.length} features in last ${commits.length} commits)`);
|
|
6674
|
+
}
|
|
6675
|
+
return patterns;
|
|
6676
|
+
}
|
|
6677
|
+
async function runMemoryCommand(options = {}) {
|
|
6678
|
+
const cwd = options.output || process.cwd();
|
|
6679
|
+
const depth = options.depth || 30;
|
|
6680
|
+
console.log(chalk19.cyan.bold("\n\u{1F9E0} Agdi Git Memory\n"));
|
|
6681
|
+
console.log(chalk19.gray(` Workspace: ${cwd}`));
|
|
6682
|
+
console.log(chalk19.gray(` Depth : last ${depth} commits
|
|
6683
|
+
`));
|
|
6684
|
+
const spinner = ora9("Reading git history...").start();
|
|
6685
|
+
try {
|
|
6686
|
+
await execAsync4("git rev-parse --is-inside-work-tree", { cwd });
|
|
6687
|
+
} catch {
|
|
6688
|
+
spinner.stop();
|
|
6689
|
+
console.log(chalk19.red(" \u2717 Not a git repository.\n"));
|
|
6690
|
+
return {
|
|
6691
|
+
repoName: path12.basename(cwd),
|
|
6692
|
+
totalCommits: 0,
|
|
6693
|
+
contributors: [],
|
|
6694
|
+
recentCommits: [],
|
|
6695
|
+
hotFiles: [],
|
|
6696
|
+
patterns: [],
|
|
6697
|
+
summary: "Not a git repository."
|
|
6698
|
+
};
|
|
6699
|
+
}
|
|
6700
|
+
let repoName = path12.basename(cwd);
|
|
6701
|
+
try {
|
|
6702
|
+
const { stdout } = await execAsync4("git remote get-url origin", { cwd });
|
|
6703
|
+
const match = stdout.trim().match(/\/([^/]+?)(?:\.git)?$/);
|
|
6704
|
+
if (match) repoName = match[1];
|
|
6705
|
+
} catch {
|
|
6706
|
+
}
|
|
6707
|
+
spinner.text = "Parsing commits...";
|
|
6708
|
+
const commits = await parseGitLog(cwd, depth);
|
|
6709
|
+
spinner.text = "Analyzing file activity...";
|
|
6710
|
+
const hotFiles = await getHotFiles(cwd, depth);
|
|
6711
|
+
spinner.text = "Identifying contributors...";
|
|
6712
|
+
const contributors = await getContributors(cwd);
|
|
6713
|
+
spinner.text = "Detecting patterns...";
|
|
6714
|
+
const patterns = detectPatterns(commits);
|
|
6715
|
+
spinner.stop();
|
|
6716
|
+
let totalCommits = commits.length;
|
|
6717
|
+
try {
|
|
6718
|
+
const { stdout } = await execAsync4("git rev-list --count HEAD", { cwd });
|
|
6719
|
+
totalCommits = parseInt(stdout.trim(), 10) || commits.length;
|
|
6720
|
+
} catch {
|
|
6721
|
+
}
|
|
6722
|
+
console.log(chalk19.green(` \u2713 Analyzed ${commits.length} commits from "${repoName}"
|
|
6723
|
+
`));
|
|
6724
|
+
console.log(chalk19.cyan.bold(" Contributors:"));
|
|
6725
|
+
contributors.slice(0, 8).forEach((c) => console.log(chalk19.gray(` \u2022 ${c}`)));
|
|
6726
|
+
if (contributors.length > 8) console.log(chalk19.gray(` ... and ${contributors.length - 8} more`));
|
|
6727
|
+
if (hotFiles.length > 0) {
|
|
6728
|
+
console.log(chalk19.cyan.bold("\n Hot Files (most changed):"));
|
|
6729
|
+
hotFiles.slice(0, 10).forEach((f) => {
|
|
6730
|
+
console.log(chalk19.gray(` ${String(f.changes).padStart(3)} changes ${f.file}`));
|
|
6731
|
+
});
|
|
6732
|
+
}
|
|
6733
|
+
if (patterns.length > 0) {
|
|
6734
|
+
console.log(chalk19.cyan.bold("\n Detected Patterns:"));
|
|
6735
|
+
patterns.forEach((p) => console.log(chalk19.yellow(` \u26A1 ${p}`)));
|
|
6736
|
+
}
|
|
6737
|
+
console.log(chalk19.cyan.bold("\n Recent Commits:"));
|
|
6738
|
+
commits.slice(0, 8).forEach((c) => {
|
|
6739
|
+
console.log(chalk19.gray(` ${c.hash} ${c.subject}`));
|
|
6740
|
+
});
|
|
6741
|
+
const summary = [
|
|
6742
|
+
`Project: ${repoName}`,
|
|
6743
|
+
`Total commits: ${totalCommits}`,
|
|
6744
|
+
`Contributors: ${contributors.join(", ")}`,
|
|
6745
|
+
`Patterns: ${patterns.join("; ") || "none detected"}`,
|
|
6746
|
+
"",
|
|
6747
|
+
"Recent activity:",
|
|
6748
|
+
...commits.slice(0, 10).map((c) => ` - ${c.subject} (${c.author}, ${c.date})`),
|
|
6749
|
+
"",
|
|
6750
|
+
"Most active files:",
|
|
6751
|
+
...hotFiles.slice(0, 10).map((f) => ` - ${f.file} (${f.changes} changes)`)
|
|
6752
|
+
].join("\n");
|
|
6753
|
+
console.log("");
|
|
6754
|
+
console.log(chalk19.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6755
|
+
console.log(chalk19.gray(` Total Commits : ${totalCommits}`));
|
|
6756
|
+
console.log(chalk19.gray(` Contributors : ${contributors.length}`));
|
|
6757
|
+
console.log(chalk19.gray(` Hot Files : ${hotFiles.length}`));
|
|
6758
|
+
console.log(chalk19.gray(` Patterns : ${patterns.length}`));
|
|
6759
|
+
console.log(chalk19.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6760
|
+
console.log("");
|
|
6761
|
+
if (options.inject) {
|
|
6762
|
+
console.log(chalk19.green(" \u{1F489} Memory context ready for Squad injection.\n"));
|
|
6763
|
+
}
|
|
6764
|
+
return {
|
|
6765
|
+
repoName,
|
|
6766
|
+
totalCommits,
|
|
6767
|
+
contributors,
|
|
6768
|
+
recentCommits: commits,
|
|
6769
|
+
hotFiles,
|
|
6770
|
+
patterns,
|
|
6771
|
+
summary
|
|
6772
|
+
};
|
|
6773
|
+
}
|
|
6774
|
+
|
|
6775
|
+
// src/commands/deps-scan.ts
|
|
6776
|
+
import chalk20 from "chalk";
|
|
6777
|
+
import ora10 from "ora";
|
|
6778
|
+
import { exec as execCb4 } from "child_process";
|
|
6779
|
+
import { promisify as promisify6 } from "util";
|
|
6780
|
+
import * as fs12 from "fs/promises";
|
|
6781
|
+
import * as path13 from "path";
|
|
6782
|
+
var execAsync5 = promisify6(execCb4);
|
|
6783
|
+
async function runNpmAudit(cwd) {
|
|
6784
|
+
try {
|
|
6785
|
+
const { stdout } = await execAsync5("npm audit --json 2>nul", {
|
|
6786
|
+
cwd,
|
|
6787
|
+
maxBuffer: 10 * 1024 * 1024
|
|
6788
|
+
});
|
|
6789
|
+
const audit = JSON.parse(stdout);
|
|
6790
|
+
const vulns = [];
|
|
6791
|
+
if (audit.vulnerabilities) {
|
|
6792
|
+
for (const [name, info] of Object.entries(audit.vulnerabilities)) {
|
|
6793
|
+
const vuln = info;
|
|
6794
|
+
vulns.push({
|
|
6795
|
+
name,
|
|
6796
|
+
severity: vuln.severity || "info",
|
|
6797
|
+
title: vuln.via?.[0]?.title || vuln.via?.[0] || "Unknown vulnerability",
|
|
6798
|
+
fixAvailable: !!vuln.fixAvailable
|
|
6799
|
+
});
|
|
6800
|
+
}
|
|
6801
|
+
}
|
|
6802
|
+
return vulns;
|
|
6803
|
+
} catch (err) {
|
|
6804
|
+
try {
|
|
6805
|
+
const output = err.stdout || "";
|
|
6806
|
+
const audit = JSON.parse(output);
|
|
6807
|
+
const vulns = [];
|
|
6808
|
+
if (audit.vulnerabilities) {
|
|
6809
|
+
for (const [name, info] of Object.entries(audit.vulnerabilities)) {
|
|
6810
|
+
const vuln = info;
|
|
6811
|
+
vulns.push({
|
|
6812
|
+
name,
|
|
6813
|
+
severity: vuln.severity || "info",
|
|
6814
|
+
title: typeof vuln.via?.[0] === "string" ? vuln.via[0] : vuln.via?.[0]?.title || "Unknown",
|
|
6815
|
+
fixAvailable: !!vuln.fixAvailable
|
|
6816
|
+
});
|
|
6817
|
+
}
|
|
6818
|
+
}
|
|
6819
|
+
return vulns;
|
|
6820
|
+
} catch {
|
|
6821
|
+
return [];
|
|
6822
|
+
}
|
|
6823
|
+
}
|
|
6824
|
+
}
|
|
6825
|
+
async function runNpmOutdated(cwd) {
|
|
6826
|
+
try {
|
|
6827
|
+
const { stdout } = await execAsync5("npm outdated --json 2>nul", {
|
|
6828
|
+
cwd,
|
|
6829
|
+
maxBuffer: 5 * 1024 * 1024
|
|
6830
|
+
});
|
|
6831
|
+
const outdated = JSON.parse(stdout || "{}");
|
|
6832
|
+
return Object.entries(outdated).map(([name, info]) => {
|
|
6833
|
+
const pkg = info;
|
|
6834
|
+
return {
|
|
6835
|
+
name,
|
|
6836
|
+
current: pkg.current || "unknown",
|
|
6837
|
+
wanted: pkg.wanted || "unknown",
|
|
6838
|
+
latest: pkg.latest || "unknown",
|
|
6839
|
+
type: pkg.type || "dependencies"
|
|
6840
|
+
};
|
|
6841
|
+
});
|
|
6842
|
+
} catch (err) {
|
|
6843
|
+
try {
|
|
6844
|
+
const output = err.stdout || "{}";
|
|
6845
|
+
const outdated = JSON.parse(output);
|
|
6846
|
+
return Object.entries(outdated).map(([name, info]) => {
|
|
6847
|
+
const pkg = info;
|
|
6848
|
+
return {
|
|
6849
|
+
name,
|
|
6850
|
+
current: pkg.current || "unknown",
|
|
6851
|
+
wanted: pkg.wanted || "unknown",
|
|
6852
|
+
latest: pkg.latest || "unknown",
|
|
6853
|
+
type: pkg.type || "dependencies"
|
|
6854
|
+
};
|
|
6855
|
+
});
|
|
6856
|
+
} catch {
|
|
6857
|
+
return [];
|
|
6858
|
+
}
|
|
6859
|
+
}
|
|
6860
|
+
}
|
|
6861
|
+
async function countDeps(cwd) {
|
|
6862
|
+
try {
|
|
6863
|
+
const pkgJson = await fs12.readFile(path13.join(cwd, "package.json"), "utf-8");
|
|
6864
|
+
const pkg = JSON.parse(pkgJson);
|
|
6865
|
+
const deps = Object.keys(pkg.dependencies || {}).length;
|
|
6866
|
+
const devDeps = Object.keys(pkg.devDependencies || {}).length;
|
|
6867
|
+
return deps + devDeps;
|
|
6868
|
+
} catch {
|
|
6869
|
+
return 0;
|
|
6870
|
+
}
|
|
6871
|
+
}
|
|
6872
|
+
async function runDepsScanCommand(options = {}) {
|
|
6873
|
+
const cwd = options.output || process.cwd();
|
|
6874
|
+
console.log(chalk20.cyan.bold("\n\u{1F6E1}\uFE0F Agdi Dependency Scanner\n"));
|
|
6875
|
+
console.log(chalk20.gray(` Workspace: ${cwd}`));
|
|
6876
|
+
console.log(chalk20.gray(` Mode : ${options.full ? chalk20.yellow("FULL") : "Quick"}
|
|
6877
|
+
`));
|
|
6878
|
+
try {
|
|
6879
|
+
await fs12.access(path13.join(cwd, "package.json"));
|
|
6880
|
+
} catch {
|
|
6881
|
+
console.log(chalk20.red(" \u2717 No package.json found in workspace.\n"));
|
|
6882
|
+
return {
|
|
6883
|
+
vulnerabilities: [],
|
|
6884
|
+
outdated: [],
|
|
6885
|
+
totalDeps: 0,
|
|
6886
|
+
summary: { critical: 0, high: 0, moderate: 0, low: 0, outdatedCount: 0, fixableCount: 0 },
|
|
6887
|
+
passed: true
|
|
6888
|
+
};
|
|
6889
|
+
}
|
|
6890
|
+
const spinner = ora10("Running vulnerability scan...").start();
|
|
6891
|
+
const vulns = await runNpmAudit(cwd);
|
|
6892
|
+
const totalDeps = await countDeps(cwd);
|
|
6893
|
+
let outdated = [];
|
|
6894
|
+
if (options.full) {
|
|
6895
|
+
spinner.text = "Checking for outdated packages...";
|
|
6896
|
+
outdated = await runNpmOutdated(cwd);
|
|
6897
|
+
}
|
|
6898
|
+
spinner.stop();
|
|
6899
|
+
const critical = vulns.filter((v) => v.severity === "critical").length;
|
|
6900
|
+
const high = vulns.filter((v) => v.severity === "high").length;
|
|
6901
|
+
const moderate = vulns.filter((v) => v.severity === "moderate").length;
|
|
6902
|
+
const low = vulns.filter((v) => v.severity === "low").length;
|
|
6903
|
+
const fixable = vulns.filter((v) => v.fixAvailable).length;
|
|
6904
|
+
if (vulns.length > 0) {
|
|
6905
|
+
console.log(chalk20.red.bold(` \u26A0 Found ${vulns.length} vulnerabilit${vulns.length === 1 ? "y" : "ies"}:
|
|
6906
|
+
`));
|
|
6907
|
+
for (const severity of ["critical", "high", "moderate", "low"]) {
|
|
6908
|
+
const items = vulns.filter((v) => v.severity === severity);
|
|
6909
|
+
if (items.length === 0) continue;
|
|
6910
|
+
const color = severity === "critical" ? chalk20.red.bold : severity === "high" ? chalk20.red : severity === "moderate" ? chalk20.yellow : chalk20.gray;
|
|
6911
|
+
console.log(color(` ${severity.toUpperCase()} (${items.length}):`));
|
|
6912
|
+
items.slice(0, 5).forEach((v) => {
|
|
6913
|
+
const fix = v.fixAvailable ? chalk20.green(" [fixable]") : chalk20.gray(" [manual]");
|
|
6914
|
+
console.log(chalk20.gray(` \u2022 ${v.name} \u2014 ${v.title}${fix}`));
|
|
6915
|
+
});
|
|
6916
|
+
if (items.length > 5) {
|
|
6917
|
+
console.log(chalk20.gray(` ... and ${items.length - 5} more`));
|
|
6918
|
+
}
|
|
6919
|
+
}
|
|
6920
|
+
} else {
|
|
6921
|
+
console.log(chalk20.green(" \u2705 No known vulnerabilities found!\n"));
|
|
6922
|
+
}
|
|
6923
|
+
if (options.full && outdated.length > 0) {
|
|
6924
|
+
console.log(chalk20.yellow.bold(`
|
|
6925
|
+
\u{1F4E6} Outdated Packages (${outdated.length}):
|
|
6926
|
+
`));
|
|
6927
|
+
outdated.slice(0, 10).forEach((pkg) => {
|
|
6928
|
+
const isMajor = pkg.current.split(".")[0] !== pkg.latest.split(".")[0];
|
|
6929
|
+
const urgency = isMajor ? chalk20.red("MAJOR") : chalk20.yellow("minor");
|
|
6930
|
+
console.log(chalk20.gray(` ${pkg.name.padEnd(30)} ${pkg.current} \u2192 ${chalk20.green(pkg.latest)} ${urgency}`));
|
|
6931
|
+
});
|
|
6932
|
+
if (outdated.length > 10) {
|
|
6933
|
+
console.log(chalk20.gray(` ... and ${outdated.length - 10} more`));
|
|
6934
|
+
}
|
|
6935
|
+
}
|
|
6936
|
+
if (options.fix && fixable > 0) {
|
|
6937
|
+
console.log(chalk20.cyan.bold("\n \u{1F527} Running npm audit fix...\n"));
|
|
6938
|
+
try {
|
|
6939
|
+
const { stdout } = await execAsync5("npm audit fix", { cwd });
|
|
6940
|
+
console.log(chalk20.gray(` ${stdout.trim().split("\n").slice(0, 5).join("\n ")}`));
|
|
6941
|
+
console.log(chalk20.green("\n \u2713 Auto-fix complete.\n"));
|
|
6942
|
+
} catch (err) {
|
|
6943
|
+
console.log(chalk20.yellow(" \u26A0 Some fixes require manual intervention.\n"));
|
|
6944
|
+
}
|
|
6945
|
+
}
|
|
6946
|
+
const passed = critical === 0 && high === 0;
|
|
6947
|
+
console.log("");
|
|
6948
|
+
console.log(chalk20.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6949
|
+
console.log(chalk20.gray(` Total Deps : ${totalDeps}`));
|
|
6950
|
+
console.log(chalk20.red(` Critical : ${critical}`));
|
|
6951
|
+
console.log(chalk20.red(` High : ${high}`));
|
|
6952
|
+
console.log(chalk20.yellow(` Moderate : ${moderate}`));
|
|
6953
|
+
console.log(chalk20.gray(` Low : ${low}`));
|
|
6954
|
+
if (options.full) {
|
|
6955
|
+
console.log(chalk20.yellow(` Outdated : ${outdated.length}`));
|
|
6956
|
+
}
|
|
6957
|
+
console.log(chalk20.green(` Fixable : ${fixable}`));
|
|
6958
|
+
console.log(chalk20.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6959
|
+
console.log(chalk20.gray(` Verdict : ${passed ? chalk20.green.bold("PASS \u2713") : chalk20.red.bold("FAIL \u2717")}`));
|
|
6960
|
+
console.log("");
|
|
6961
|
+
if (!passed) {
|
|
6962
|
+
console.log(chalk20.yellow(" \u{1F4A1} Run `agdi deps --fix` to auto-fix vulnerabilities.\n"));
|
|
6963
|
+
}
|
|
6964
|
+
return {
|
|
6965
|
+
vulnerabilities: vulns,
|
|
6966
|
+
outdated,
|
|
6967
|
+
totalDeps,
|
|
6968
|
+
summary: { critical, high, moderate, low, outdatedCount: outdated.length, fixableCount: fixable },
|
|
6969
|
+
passed
|
|
6970
|
+
};
|
|
6971
|
+
}
|
|
6972
|
+
|
|
6973
|
+
// src/commands/plugin-loader.ts
|
|
6974
|
+
import chalk21 from "chalk";
|
|
6975
|
+
import * as fs13 from "fs/promises";
|
|
6976
|
+
import * as path14 from "path";
|
|
6977
|
+
import { exec as execCb5 } from "child_process";
|
|
6978
|
+
import { promisify as promisify7 } from "util";
|
|
6979
|
+
var execAsync6 = promisify7(execCb5);
|
|
6980
|
+
async function discoverLocalPlugins(cwd) {
|
|
6981
|
+
const pluginDir = path14.join(cwd, ".agdi", "plugins");
|
|
6982
|
+
const plugins = [];
|
|
6983
|
+
try {
|
|
6984
|
+
const entries = await fs13.readdir(pluginDir, { withFileTypes: true });
|
|
6985
|
+
for (const entry of entries) {
|
|
6986
|
+
if (!entry.isDirectory()) continue;
|
|
6987
|
+
const pluginPath = path14.join(pluginDir, entry.name);
|
|
6988
|
+
const manifestPath = path14.join(pluginPath, "agdi-plugin.json");
|
|
6989
|
+
try {
|
|
6990
|
+
const raw = await fs13.readFile(manifestPath, "utf-8");
|
|
6991
|
+
const manifest = JSON.parse(raw);
|
|
6992
|
+
const entryExists = await fs13.access(path14.join(pluginPath, manifest.entryPoint)).then(() => true).catch(() => false);
|
|
6993
|
+
plugins.push({
|
|
6994
|
+
manifest,
|
|
6995
|
+
path: pluginPath,
|
|
6996
|
+
source: "local",
|
|
6997
|
+
healthy: entryExists
|
|
6998
|
+
});
|
|
6999
|
+
} catch {
|
|
7000
|
+
}
|
|
7001
|
+
}
|
|
7002
|
+
} catch {
|
|
7003
|
+
}
|
|
7004
|
+
return plugins;
|
|
7005
|
+
}
|
|
7006
|
+
async function discoverNpmPlugins(cwd) {
|
|
7007
|
+
const plugins = [];
|
|
7008
|
+
try {
|
|
7009
|
+
const nodeModules = path14.join(cwd, "node_modules");
|
|
7010
|
+
const entries = await fs13.readdir(nodeModules, { withFileTypes: true });
|
|
7011
|
+
for (const entry of entries) {
|
|
7012
|
+
if (!entry.isDirectory() || !entry.name.startsWith("agdi-plugin-")) continue;
|
|
7013
|
+
const pluginPath = path14.join(nodeModules, entry.name);
|
|
7014
|
+
const manifestPath = path14.join(pluginPath, "agdi-plugin.json");
|
|
7015
|
+
try {
|
|
7016
|
+
const raw = await fs13.readFile(manifestPath, "utf-8");
|
|
7017
|
+
const manifest = JSON.parse(raw);
|
|
7018
|
+
plugins.push({
|
|
7019
|
+
manifest,
|
|
7020
|
+
path: pluginPath,
|
|
7021
|
+
source: "npm",
|
|
7022
|
+
healthy: true
|
|
7023
|
+
});
|
|
7024
|
+
} catch {
|
|
7025
|
+
try {
|
|
7026
|
+
const pkgRaw = await fs13.readFile(path14.join(pluginPath, "package.json"), "utf-8");
|
|
7027
|
+
const pkg = JSON.parse(pkgRaw);
|
|
7028
|
+
if (pkg.agdiPlugin) {
|
|
7029
|
+
plugins.push({
|
|
7030
|
+
manifest: {
|
|
7031
|
+
name: pkg.name,
|
|
7032
|
+
version: pkg.version,
|
|
7033
|
+
description: pkg.description || "",
|
|
7034
|
+
entryPoint: pkg.main || "index.js",
|
|
7035
|
+
capabilities: pkg.agdiPlugin.capabilities || [],
|
|
7036
|
+
author: pkg.author || "unknown"
|
|
7037
|
+
},
|
|
7038
|
+
path: pluginPath,
|
|
7039
|
+
source: "npm",
|
|
7040
|
+
healthy: true
|
|
7041
|
+
});
|
|
7042
|
+
}
|
|
7043
|
+
} catch {
|
|
7044
|
+
}
|
|
7045
|
+
}
|
|
7046
|
+
}
|
|
7047
|
+
} catch {
|
|
7048
|
+
}
|
|
7049
|
+
return plugins;
|
|
7050
|
+
}
|
|
7051
|
+
async function createPluginTemplate(cwd) {
|
|
7052
|
+
const pluginDir = path14.join(cwd, ".agdi", "plugins", "my-custom-agent");
|
|
7053
|
+
await fs13.mkdir(pluginDir, { recursive: true });
|
|
7054
|
+
const manifest = {
|
|
7055
|
+
name: "agdi-plugin-custom",
|
|
7056
|
+
version: "1.0.0",
|
|
7057
|
+
description: "My custom Agdi plugin",
|
|
7058
|
+
agent: "custom",
|
|
7059
|
+
entryPoint: "./index.js",
|
|
7060
|
+
capabilities: ["frontend"],
|
|
7061
|
+
author: "developer"
|
|
7062
|
+
};
|
|
7063
|
+
await fs13.writeFile(
|
|
7064
|
+
path14.join(pluginDir, "agdi-plugin.json"),
|
|
7065
|
+
JSON.stringify(manifest, null, 2),
|
|
7066
|
+
"utf-8"
|
|
7067
|
+
);
|
|
7068
|
+
const entryPoint = `/**
|
|
7069
|
+
* Agdi Custom Plugin
|
|
7070
|
+
*
|
|
7071
|
+
* This plugin adds a custom agent capability to Agdi.
|
|
7072
|
+
* Modify the \`execute\` function to define your agent's behavior.
|
|
7073
|
+
*/
|
|
7074
|
+
|
|
7075
|
+
module.exports = {
|
|
7076
|
+
name: 'custom',
|
|
7077
|
+
|
|
7078
|
+
/**
|
|
7079
|
+
* Called when the Squad assigns a task to this agent.
|
|
7080
|
+
* @param {object} task - The task object { id, title, description, ... }
|
|
7081
|
+
* @param {object} context - The shared context { projectSpec, workspaceRoot, ... }
|
|
7082
|
+
* @returns {object} - Agent output { success, content, files, errors }
|
|
7083
|
+
*/
|
|
7084
|
+
async execute(task, context) {
|
|
7085
|
+
return {
|
|
7086
|
+
success: true,
|
|
7087
|
+
content: 'Custom agent executed successfully!',
|
|
7088
|
+
files: [],
|
|
7089
|
+
errors: [],
|
|
7090
|
+
};
|
|
7091
|
+
},
|
|
7092
|
+
|
|
7093
|
+
/**
|
|
7094
|
+
* System prompt for the LLM when this agent is active.
|
|
7095
|
+
*/
|
|
7096
|
+
getSystemPrompt() {
|
|
7097
|
+
return 'You are a custom agent. Follow the project architecture rules.';
|
|
7098
|
+
},
|
|
7099
|
+
};
|
|
7100
|
+
`;
|
|
7101
|
+
await fs13.writeFile(path14.join(pluginDir, "index.js"), entryPoint, "utf-8");
|
|
7102
|
+
return pluginDir;
|
|
7103
|
+
}
|
|
7104
|
+
async function runPluginsCommand(options = {}) {
|
|
7105
|
+
const cwd = options.output || process.cwd();
|
|
7106
|
+
console.log(chalk21.cyan.bold("\n\u{1F50C} Agdi Plugin Manager\n"));
|
|
7107
|
+
if (options.init) {
|
|
7108
|
+
const pluginPath = await createPluginTemplate(cwd);
|
|
7109
|
+
console.log(chalk21.green(` \u2713 Plugin template created at:`));
|
|
7110
|
+
console.log(chalk21.gray(` ${path14.relative(cwd, pluginPath)}/
|
|
7111
|
+
`));
|
|
7112
|
+
console.log(chalk21.gray(" Files created:"));
|
|
7113
|
+
console.log(chalk21.gray(" \u2022 agdi-plugin.json (manifest)"));
|
|
7114
|
+
console.log(chalk21.gray(" \u2022 index.js (entry point)\n"));
|
|
7115
|
+
console.log(chalk21.yellow(" Next steps:"));
|
|
7116
|
+
console.log(chalk21.gray(" 1. Edit agdi-plugin.json with your plugin details"));
|
|
7117
|
+
console.log(chalk21.gray(" 2. Implement your agent logic in index.js"));
|
|
7118
|
+
console.log(chalk21.gray(" 3. Run `agdi plugins` to verify it loads\n"));
|
|
7119
|
+
return;
|
|
7120
|
+
}
|
|
7121
|
+
if (options.install) {
|
|
7122
|
+
const pkgName = options.install.startsWith("agdi-plugin-") ? options.install : `agdi-plugin-${options.install}`;
|
|
7123
|
+
console.log(chalk21.gray(` Installing ${pkgName}...`));
|
|
7124
|
+
try {
|
|
7125
|
+
await execAsync6(`npm install ${pkgName}`, { cwd });
|
|
7126
|
+
console.log(chalk21.green(` \u2713 ${pkgName} installed successfully.
|
|
7127
|
+
`));
|
|
7128
|
+
} catch (err) {
|
|
7129
|
+
console.log(chalk21.red(` \u2717 Failed to install ${pkgName}.`));
|
|
7130
|
+
console.log(chalk21.gray(` ${err.message?.split("\n")[0]}
|
|
7131
|
+
`));
|
|
7132
|
+
}
|
|
7133
|
+
return;
|
|
7134
|
+
}
|
|
7135
|
+
const localPlugins = await discoverLocalPlugins(cwd);
|
|
7136
|
+
const npmPlugins = await discoverNpmPlugins(cwd);
|
|
7137
|
+
const allPlugins = [...localPlugins, ...npmPlugins];
|
|
7138
|
+
if (allPlugins.length === 0) {
|
|
7139
|
+
console.log(chalk21.gray(" No plugins installed.\n"));
|
|
7140
|
+
console.log(chalk21.yellow(" Get started:"));
|
|
7141
|
+
console.log(chalk21.gray(" \u2022 Create a plugin: agdi plugins --init"));
|
|
7142
|
+
console.log(chalk21.gray(" \u2022 Install from npm: agdi plugins --install <name>\n"));
|
|
7143
|
+
return;
|
|
7144
|
+
}
|
|
7145
|
+
console.log(chalk21.gray(` Found ${allPlugins.length} plugin(s):
|
|
7146
|
+
`));
|
|
7147
|
+
for (const plugin of allPlugins) {
|
|
7148
|
+
const status = plugin.healthy ? chalk21.green("\u2713") : chalk21.red("\u2717");
|
|
7149
|
+
const source = plugin.source === "local" ? chalk21.blue("local") : chalk21.magenta("npm");
|
|
7150
|
+
console.log(` ${status} ${chalk21.white.bold(plugin.manifest.name)} ${chalk21.gray(`v${plugin.manifest.version}`)} ${source}`);
|
|
7151
|
+
console.log(chalk21.gray(` ${plugin.manifest.description}`));
|
|
7152
|
+
if (plugin.manifest.capabilities.length > 0) {
|
|
7153
|
+
console.log(chalk21.gray(` Capabilities: ${plugin.manifest.capabilities.join(", ")}`));
|
|
7154
|
+
}
|
|
7155
|
+
}
|
|
7156
|
+
console.log("");
|
|
7157
|
+
console.log(chalk21.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
7158
|
+
console.log(chalk21.gray(` Local : ${localPlugins.length}`));
|
|
7159
|
+
console.log(chalk21.gray(` NPM : ${npmPlugins.length}`));
|
|
7160
|
+
console.log(chalk21.gray(` Healthy : ${allPlugins.filter((p) => p.healthy).length}/${allPlugins.length}`));
|
|
7161
|
+
console.log(chalk21.cyan(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
7162
|
+
console.log("");
|
|
7163
|
+
}
|
|
7164
|
+
|
|
7165
|
+
// src/commands/features.ts
|
|
7166
|
+
import chalk22 from "chalk";
|
|
7167
|
+
import ora11 from "ora";
|
|
7168
|
+
import fsExtra from "fs-extra";
|
|
7169
|
+
import { resolve as resolve2, join as join10 } from "path";
|
|
7170
|
+
var DIRECTORY_COPY_TARGETS = [
|
|
7171
|
+
{ source: "skills", target: "skills" },
|
|
7172
|
+
{ source: "extensions", target: "extensions" },
|
|
7173
|
+
{ source: "src/routing", target: "src/routing" },
|
|
7174
|
+
{ source: "src/pairing", target: "src/pairing" },
|
|
7175
|
+
{ source: "src/channels/plugins", target: "src/channels/plugins" },
|
|
7176
|
+
{ source: "src/channels/allowlists", target: "src/channels/allowlists" }
|
|
7177
|
+
];
|
|
7178
|
+
var FILE_COPY_TARGETS = [
|
|
7179
|
+
{ source: "src/channels/allowlist-match.ts", target: "src/channels/allowlist-match.ts" },
|
|
7180
|
+
{ source: "src/channels/channel-config.ts", target: "src/channels/channel-config.ts" }
|
|
7181
|
+
];
|
|
7182
|
+
var ALL_DIRECTORY_COPY_TARGETS = [
|
|
7183
|
+
{ source: "dist", target: "dist" },
|
|
7184
|
+
{ source: "skills", target: "skills" },
|
|
7185
|
+
{ source: "extensions", target: "extensions" },
|
|
7186
|
+
{ source: "src", target: "src" },
|
|
7187
|
+
{ source: "scripts", target: "scripts" },
|
|
7188
|
+
{ source: "docs", target: "docs" },
|
|
7189
|
+
{ source: "test", target: "test" },
|
|
7190
|
+
{ source: "apps", target: "apps" },
|
|
7191
|
+
{ source: "assets", target: "assets" },
|
|
7192
|
+
{ source: "prisma", target: "prisma" },
|
|
7193
|
+
{ source: "ui", target: "ui" },
|
|
7194
|
+
{ source: "vendor", target: "vendor" },
|
|
7195
|
+
{ source: "website", target: "website" }
|
|
7196
|
+
];
|
|
7197
|
+
var ALL_FILE_COPY_TARGETS = [
|
|
7198
|
+
{ source: "agdi.mjs", target: "agdi.mjs" },
|
|
7199
|
+
{ source: "README.md", target: "README.md" },
|
|
7200
|
+
{ source: "LICENSE", target: "LICENSE" },
|
|
7201
|
+
{ source: "CHANGELOG.md", target: "CHANGELOG.md" },
|
|
7202
|
+
{ source: "SECURITY.md", target: "SECURITY.md" },
|
|
7203
|
+
{ source: "CONTRIBUTING.md", target: "CONTRIBUTING.md" },
|
|
7204
|
+
{ source: "pnpm-lock.yaml", target: "pnpm-lock.yaml" },
|
|
7205
|
+
{ source: "pnpm-workspace.yaml", target: "pnpm-workspace.yaml" },
|
|
7206
|
+
{ source: "tsconfig.json", target: "tsconfig.json" },
|
|
7207
|
+
{ source: "tsconfig.test.json", target: "tsconfig.test.json" },
|
|
7208
|
+
{ source: "vitest.config.ts", target: "vitest.config.ts" },
|
|
7209
|
+
{ source: "vitest.e2e.config.ts", target: "vitest.e2e.config.ts" },
|
|
7210
|
+
{ source: "vitest.unit.config.ts", target: "vitest.unit.config.ts" }
|
|
7211
|
+
];
|
|
7212
|
+
var EXCLUDED_PATH_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next"]);
|
|
7213
|
+
var EXCLUDED_ALL_PATH_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", ".git", ".next"]);
|
|
7214
|
+
function shouldIncludePath(filePath) {
|
|
7215
|
+
const normalized = filePath.replace(/\\/g, "/").toLowerCase();
|
|
7216
|
+
const segments = normalized.split("/");
|
|
7217
|
+
return !segments.some((segment) => EXCLUDED_PATH_SEGMENTS.has(segment));
|
|
7218
|
+
}
|
|
7219
|
+
async function copyTargetPaths(sourceRepo, targetRoot) {
|
|
7220
|
+
for (const target of DIRECTORY_COPY_TARGETS) {
|
|
7221
|
+
const sourcePath = join10(sourceRepo, target.source);
|
|
7222
|
+
const targetPath = join10(targetRoot, target.target);
|
|
7223
|
+
if (!await fsExtra.pathExists(sourcePath)) {
|
|
7224
|
+
continue;
|
|
7225
|
+
}
|
|
7226
|
+
await fsExtra.ensureDir(targetPath);
|
|
7227
|
+
await fsExtra.copy(sourcePath, targetPath, {
|
|
7228
|
+
overwrite: true,
|
|
7229
|
+
filter: (source) => shouldIncludePath(source)
|
|
7230
|
+
});
|
|
7231
|
+
}
|
|
7232
|
+
for (const target of FILE_COPY_TARGETS) {
|
|
7233
|
+
const sourcePath = join10(sourceRepo, target.source);
|
|
7234
|
+
const targetPath = join10(targetRoot, target.target);
|
|
7235
|
+
if (!await fsExtra.pathExists(sourcePath)) {
|
|
7236
|
+
continue;
|
|
7237
|
+
}
|
|
7238
|
+
await fsExtra.ensureDir(join10(targetPath, ".."));
|
|
7239
|
+
await fsExtra.copy(sourcePath, targetPath, { overwrite: true });
|
|
7240
|
+
}
|
|
7241
|
+
}
|
|
7242
|
+
function shouldIncludeAllPath(filePath) {
|
|
7243
|
+
const normalized = filePath.replace(/\\/g, "/").toLowerCase();
|
|
7244
|
+
const segments = normalized.split("/");
|
|
7245
|
+
return !segments.some((segment) => EXCLUDED_ALL_PATH_SEGMENTS.has(segment));
|
|
7246
|
+
}
|
|
7247
|
+
async function copyAllTargetPaths(sourceRepo, targetRoot) {
|
|
7248
|
+
for (const target of ALL_DIRECTORY_COPY_TARGETS) {
|
|
7249
|
+
const sourcePath = join10(sourceRepo, target.source);
|
|
7250
|
+
const targetPath = join10(targetRoot, target.target);
|
|
7251
|
+
if (!await fsExtra.pathExists(sourcePath)) {
|
|
7252
|
+
continue;
|
|
7253
|
+
}
|
|
7254
|
+
await fsExtra.ensureDir(targetPath);
|
|
7255
|
+
await fsExtra.copy(sourcePath, targetPath, {
|
|
7256
|
+
overwrite: true,
|
|
7257
|
+
filter: (source) => shouldIncludeAllPath(source)
|
|
7258
|
+
});
|
|
7259
|
+
}
|
|
7260
|
+
for (const target of ALL_FILE_COPY_TARGETS) {
|
|
7261
|
+
const sourcePath = join10(sourceRepo, target.source);
|
|
7262
|
+
const targetPath = join10(targetRoot, target.target);
|
|
7263
|
+
if (!await fsExtra.pathExists(sourcePath)) {
|
|
7264
|
+
continue;
|
|
7265
|
+
}
|
|
7266
|
+
await fsExtra.ensureDir(join10(targetPath, ".."));
|
|
7267
|
+
await fsExtra.copy(sourcePath, targetPath, { overwrite: true });
|
|
7268
|
+
}
|
|
7269
|
+
}
|
|
7270
|
+
async function writeMirrorPackageJson(targetRoot) {
|
|
7271
|
+
const packageJsonPath = join10(targetRoot, "package.json");
|
|
7272
|
+
const packageJson = {
|
|
7273
|
+
name: "agdi-upstream-mirror",
|
|
5757
7274
|
private: true,
|
|
5758
7275
|
type: "module",
|
|
5759
7276
|
description: "Mirrored upstream Agdi feature/runtime surface for Agdi-dev"
|
|
@@ -5761,8 +7278,8 @@ async function writeMirrorPackageJson(targetRoot) {
|
|
|
5761
7278
|
await fsExtra.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
5762
7279
|
}
|
|
5763
7280
|
async function ensureMirrorNodeModules(sourceRepo, targetRoot) {
|
|
5764
|
-
const sourceNodeModules =
|
|
5765
|
-
const targetNodeModules =
|
|
7281
|
+
const sourceNodeModules = join10(sourceRepo, "node_modules");
|
|
7282
|
+
const targetNodeModules = join10(targetRoot, "node_modules");
|
|
5766
7283
|
if (!await fsExtra.pathExists(sourceNodeModules)) {
|
|
5767
7284
|
return "missing";
|
|
5768
7285
|
}
|
|
@@ -5785,7 +7302,7 @@ async function ensureMirrorNodeModules(sourceRepo, targetRoot) {
|
|
|
5785
7302
|
}
|
|
5786
7303
|
}
|
|
5787
7304
|
async function listSkills(targetRoot) {
|
|
5788
|
-
const skillsRoot =
|
|
7305
|
+
const skillsRoot = join10(targetRoot, "skills");
|
|
5789
7306
|
if (!await fsExtra.pathExists(skillsRoot)) {
|
|
5790
7307
|
return [];
|
|
5791
7308
|
}
|
|
@@ -5796,7 +7313,7 @@ async function listSkills(targetRoot) {
|
|
|
5796
7313
|
continue;
|
|
5797
7314
|
}
|
|
5798
7315
|
const skillName = entry.name;
|
|
5799
|
-
const skillFile =
|
|
7316
|
+
const skillFile = join10(skillsRoot, skillName, "SKILL.md");
|
|
5800
7317
|
if (await fsExtra.pathExists(skillFile)) {
|
|
5801
7318
|
result.push(skillName);
|
|
5802
7319
|
}
|
|
@@ -5804,7 +7321,7 @@ async function listSkills(targetRoot) {
|
|
|
5804
7321
|
return result.sort();
|
|
5805
7322
|
}
|
|
5806
7323
|
async function listExtensions(targetRoot) {
|
|
5807
|
-
const extensionsRoot =
|
|
7324
|
+
const extensionsRoot = join10(targetRoot, "extensions");
|
|
5808
7325
|
if (!await fsExtra.pathExists(extensionsRoot)) {
|
|
5809
7326
|
return [];
|
|
5810
7327
|
}
|
|
@@ -5814,7 +7331,7 @@ async function listExtensions(targetRoot) {
|
|
|
5814
7331
|
if (!entry.isDirectory()) {
|
|
5815
7332
|
continue;
|
|
5816
7333
|
}
|
|
5817
|
-
const manifestPath =
|
|
7334
|
+
const manifestPath = join10(extensionsRoot, entry.name, "agdi.plugin.json");
|
|
5818
7335
|
if (!await fsExtra.pathExists(manifestPath)) {
|
|
5819
7336
|
continue;
|
|
5820
7337
|
}
|
|
@@ -5831,7 +7348,7 @@ async function listExtensions(targetRoot) {
|
|
|
5831
7348
|
return result.sort();
|
|
5832
7349
|
}
|
|
5833
7350
|
async function listTypeScriptFiles(targetRoot, relativePath) {
|
|
5834
|
-
const root =
|
|
7351
|
+
const root = join10(targetRoot, relativePath);
|
|
5835
7352
|
if (!await fsExtra.pathExists(root)) {
|
|
5836
7353
|
return 0;
|
|
5837
7354
|
}
|
|
@@ -5844,7 +7361,7 @@ async function listTypeScriptFiles(targetRoot, relativePath) {
|
|
|
5844
7361
|
}
|
|
5845
7362
|
const entries = await fsExtra.readdir(current, { withFileTypes: true });
|
|
5846
7363
|
for (const entry of entries) {
|
|
5847
|
-
const absolutePath =
|
|
7364
|
+
const absolutePath = join10(current, entry.name);
|
|
5848
7365
|
if (!shouldIncludePath(absolutePath)) {
|
|
5849
7366
|
continue;
|
|
5850
7367
|
}
|
|
@@ -5864,17 +7381,17 @@ async function runFeaturesCommand(options) {
|
|
|
5864
7381
|
const sourceRepo = resolve2(cwd, options.from ?? "../Agdi");
|
|
5865
7382
|
const targetRoot = resolve2(cwd, options.to ?? "./upstream/agdi");
|
|
5866
7383
|
console.log("");
|
|
5867
|
-
console.log(
|
|
5868
|
-
console.log(
|
|
5869
|
-
console.log(
|
|
7384
|
+
console.log(chalk22.cyan.bold(options.all ? "Feature Mirror" : "Feature Packs"));
|
|
7385
|
+
console.log(chalk22.gray(` Source: ${sourceRepo}`));
|
|
7386
|
+
console.log(chalk22.gray(` Target: ${targetRoot}`));
|
|
5870
7387
|
console.log("");
|
|
5871
7388
|
if (options.sync) {
|
|
5872
|
-
const spinner =
|
|
7389
|
+
const spinner = ora11(
|
|
5873
7390
|
options.all ? "Syncing full upstream Agdi mirror..." : "Syncing feature packs from Agdi..."
|
|
5874
7391
|
).start();
|
|
5875
7392
|
if (!await fsExtra.pathExists(sourceRepo)) {
|
|
5876
7393
|
spinner.fail("Source Agdi repository not found");
|
|
5877
|
-
console.log(
|
|
7394
|
+
console.log(chalk22.red(`Expected source path: ${sourceRepo}`));
|
|
5878
7395
|
return;
|
|
5879
7396
|
}
|
|
5880
7397
|
try {
|
|
@@ -5890,17 +7407,17 @@ async function runFeaturesCommand(options) {
|
|
|
5890
7407
|
spinner.succeed("Sync completed");
|
|
5891
7408
|
if (options.all && nodeModulesStatus !== null) {
|
|
5892
7409
|
if (nodeModulesStatus === "linked") {
|
|
5893
|
-
console.log(
|
|
7410
|
+
console.log(chalk22.gray(" Mirror node_modules: linked to source repo"));
|
|
5894
7411
|
} else if (nodeModulesStatus === "missing") {
|
|
5895
|
-
console.log(
|
|
7412
|
+
console.log(chalk22.yellow(" Mirror node_modules: source node_modules missing"));
|
|
5896
7413
|
} else {
|
|
5897
|
-
console.log(
|
|
7414
|
+
console.log(chalk22.yellow(" Mirror node_modules: failed to link (proxy commands may fail)"));
|
|
5898
7415
|
}
|
|
5899
7416
|
}
|
|
5900
7417
|
} catch (error) {
|
|
5901
7418
|
spinner.fail("Sync failed");
|
|
5902
7419
|
const message = error instanceof Error ? error.message : String(error);
|
|
5903
|
-
console.log(
|
|
7420
|
+
console.log(chalk22.red(message));
|
|
5904
7421
|
return;
|
|
5905
7422
|
}
|
|
5906
7423
|
}
|
|
@@ -5911,53 +7428,53 @@ async function runFeaturesCommand(options) {
|
|
|
5911
7428
|
listTypeScriptFiles(targetRoot, "src/pairing"),
|
|
5912
7429
|
listTypeScriptFiles(targetRoot, "src/channels")
|
|
5913
7430
|
]);
|
|
5914
|
-
console.log(
|
|
5915
|
-
console.log(
|
|
5916
|
-
console.log(
|
|
5917
|
-
console.log(
|
|
5918
|
-
console.log(
|
|
5919
|
-
console.log(
|
|
7431
|
+
console.log(chalk22.white.bold("Imported capabilities:"));
|
|
7432
|
+
console.log(chalk22.gray(` Skills: ${skills.length}`));
|
|
7433
|
+
console.log(chalk22.gray(` Channel extensions: ${extensions.length}`));
|
|
7434
|
+
console.log(chalk22.gray(` Routing TS files: ${routingTs}`));
|
|
7435
|
+
console.log(chalk22.gray(` Pairing TS files: ${pairingTs}`));
|
|
7436
|
+
console.log(chalk22.gray(` Channel policy TS files: ${channelPolicyTs}`));
|
|
5920
7437
|
console.log("");
|
|
5921
7438
|
if (options.all) {
|
|
5922
|
-
const hasRuntimeEntry = await fsExtra.pathExists(
|
|
5923
|
-
const hasRuntimeDist = await fsExtra.pathExists(
|
|
5924
|
-
console.log(
|
|
5925
|
-
console.log(
|
|
5926
|
-
console.log(
|
|
7439
|
+
const hasRuntimeEntry = await fsExtra.pathExists(join10(targetRoot, "agdi.mjs"));
|
|
7440
|
+
const hasRuntimeDist = await fsExtra.pathExists(join10(targetRoot, "dist/entry.js"));
|
|
7441
|
+
console.log(chalk22.white.bold("Mirror runtime:"));
|
|
7442
|
+
console.log(chalk22.gray(` agdi.mjs present: ${hasRuntimeEntry ? "yes" : "no"}`));
|
|
7443
|
+
console.log(chalk22.gray(` dist/entry.js present: ${hasRuntimeDist ? "yes" : "no"}`));
|
|
5927
7444
|
console.log("");
|
|
5928
7445
|
}
|
|
5929
7446
|
if (extensions.length > 0) {
|
|
5930
|
-
console.log(
|
|
5931
|
-
console.log(
|
|
7447
|
+
console.log(chalk22.white.bold("Top extensions:"));
|
|
7448
|
+
console.log(chalk22.gray(` ${extensions.slice(0, 16).join(", ")}`));
|
|
5932
7449
|
console.log("");
|
|
5933
7450
|
}
|
|
5934
7451
|
if (skills.length > 0) {
|
|
5935
|
-
console.log(
|
|
5936
|
-
console.log(
|
|
7452
|
+
console.log(chalk22.white.bold("Top skills:"));
|
|
7453
|
+
console.log(chalk22.gray(` ${skills.slice(0, 16).join(", ")}`));
|
|
5937
7454
|
console.log("");
|
|
5938
7455
|
}
|
|
5939
7456
|
}
|
|
5940
7457
|
|
|
5941
7458
|
// src/commands/channels.ts
|
|
5942
|
-
import
|
|
5943
|
-
import
|
|
5944
|
-
import
|
|
7459
|
+
import chalk23 from "chalk";
|
|
7460
|
+
import fs16 from "fs-extra";
|
|
7461
|
+
import path18 from "path";
|
|
5945
7462
|
|
|
5946
7463
|
// src/extensions/channel-runtime.ts
|
|
5947
7464
|
import os3 from "os";
|
|
5948
|
-
import
|
|
7465
|
+
import path17 from "path";
|
|
5949
7466
|
|
|
5950
7467
|
// src/extensions/registry.ts
|
|
5951
|
-
import
|
|
5952
|
-
import
|
|
7468
|
+
import fs14 from "fs-extra";
|
|
7469
|
+
import path15 from "path";
|
|
5953
7470
|
function resolveUpstreamExtensionsRoot(startDir = process.cwd()) {
|
|
5954
|
-
let current =
|
|
7471
|
+
let current = path15.resolve(startDir);
|
|
5955
7472
|
while (true) {
|
|
5956
|
-
const candidate =
|
|
5957
|
-
if (
|
|
7473
|
+
const candidate = path15.join(current, "upstream", "agdi", "extensions");
|
|
7474
|
+
if (fs14.existsSync(candidate)) {
|
|
5958
7475
|
return candidate;
|
|
5959
7476
|
}
|
|
5960
|
-
const parent =
|
|
7477
|
+
const parent = path15.dirname(current);
|
|
5961
7478
|
if (parent === current) {
|
|
5962
7479
|
break;
|
|
5963
7480
|
}
|
|
@@ -5966,22 +7483,22 @@ function resolveUpstreamExtensionsRoot(startDir = process.cwd()) {
|
|
|
5966
7483
|
return null;
|
|
5967
7484
|
}
|
|
5968
7485
|
async function discoverExtensions(extensionsRoot) {
|
|
5969
|
-
if (!await
|
|
7486
|
+
if (!await fs14.pathExists(extensionsRoot)) {
|
|
5970
7487
|
return [];
|
|
5971
7488
|
}
|
|
5972
|
-
const entries = await
|
|
7489
|
+
const entries = await fs14.readdir(extensionsRoot, { withFileTypes: true });
|
|
5973
7490
|
const discovered = [];
|
|
5974
7491
|
for (const entry of entries) {
|
|
5975
7492
|
if (!entry.isDirectory()) {
|
|
5976
7493
|
continue;
|
|
5977
7494
|
}
|
|
5978
|
-
const extensionDir =
|
|
5979
|
-
const manifestPath =
|
|
5980
|
-
if (!await
|
|
7495
|
+
const extensionDir = path15.join(extensionsRoot, entry.name);
|
|
7496
|
+
const manifestPath = path15.join(extensionDir, "agdi.plugin.json");
|
|
7497
|
+
if (!await fs14.pathExists(manifestPath)) {
|
|
5981
7498
|
continue;
|
|
5982
7499
|
}
|
|
5983
7500
|
try {
|
|
5984
|
-
const manifest = await
|
|
7501
|
+
const manifest = await fs14.readJson(manifestPath);
|
|
5985
7502
|
if (!manifest || typeof manifest.id !== "string" || manifest.id.trim().length === 0) {
|
|
5986
7503
|
continue;
|
|
5987
7504
|
}
|
|
@@ -6017,12 +7534,12 @@ function discoverChannels(extensions) {
|
|
|
6017
7534
|
}
|
|
6018
7535
|
|
|
6019
7536
|
// src/extensions/whatsapp-runtime.ts
|
|
6020
|
-
import
|
|
7537
|
+
import fs15 from "fs-extra";
|
|
6021
7538
|
import os2 from "os";
|
|
6022
|
-
import
|
|
7539
|
+
import path16 from "path";
|
|
6023
7540
|
function expandHomeDir(input6) {
|
|
6024
7541
|
if (input6.startsWith("~/") || input6 === "~") {
|
|
6025
|
-
return
|
|
7542
|
+
return path16.join(os2.homedir(), input6.slice(2));
|
|
6026
7543
|
}
|
|
6027
7544
|
return input6;
|
|
6028
7545
|
}
|
|
@@ -6045,10 +7562,10 @@ async function closeSocket(socket) {
|
|
|
6045
7562
|
}
|
|
6046
7563
|
async function sendWhatsAppTextMessage(options) {
|
|
6047
7564
|
const targetJid = normalizeWhatsAppJid(options.to);
|
|
6048
|
-
const authDir =
|
|
7565
|
+
const authDir = path16.resolve(expandHomeDir(options.authDir ?? path16.join(os2.homedir(), ".agdi", "whatsapp-auth")));
|
|
6049
7566
|
const timeoutMs = options.timeoutMs ?? 9e4;
|
|
6050
7567
|
const printQr = options.printQr ?? true;
|
|
6051
|
-
await
|
|
7568
|
+
await fs15.ensureDir(authDir);
|
|
6052
7569
|
const baileys = await import("./lib-2XISBYT3.js");
|
|
6053
7570
|
const makeWASocket = baileys.makeWASocket;
|
|
6054
7571
|
const useMultiFileAuthState = baileys.useMultiFileAuthState;
|
|
@@ -6152,7 +7669,7 @@ function expandHomeDir2(input6) {
|
|
|
6152
7669
|
return os3.homedir();
|
|
6153
7670
|
}
|
|
6154
7671
|
if (input6.startsWith("~/")) {
|
|
6155
|
-
return
|
|
7672
|
+
return path17.join(os3.homedir(), input6.slice(2));
|
|
6156
7673
|
}
|
|
6157
7674
|
return input6;
|
|
6158
7675
|
}
|
|
@@ -6164,8 +7681,8 @@ function normalizeBaseUrl(url) {
|
|
|
6164
7681
|
return cleaned.replace(/\/+$/, "");
|
|
6165
7682
|
}
|
|
6166
7683
|
function resolveChannelRuntimeConfig(config) {
|
|
6167
|
-
const whatsappAuthDirRaw = cleanValue(process.env.AGDI_WHATSAPP_AUTH_DIR) || cleanValue(config.channelRuntime?.whatsapp?.authDir) ||
|
|
6168
|
-
const whatsappAuthDir =
|
|
7684
|
+
const whatsappAuthDirRaw = cleanValue(process.env.AGDI_WHATSAPP_AUTH_DIR) || cleanValue(config.channelRuntime?.whatsapp?.authDir) || path17.join(os3.homedir(), ".agdi", "whatsapp-auth");
|
|
7685
|
+
const whatsappAuthDir = path17.resolve(expandHomeDir2(whatsappAuthDirRaw));
|
|
6169
7686
|
return {
|
|
6170
7687
|
slackWebhook: cleanValue(process.env.AGDI_WEBHOOK_SLACK) || cleanValue(config.channelRuntime?.slack?.webhookUrl),
|
|
6171
7688
|
discordWebhook: cleanValue(process.env.AGDI_WEBHOOK_DISCORD) || cleanValue(config.channelRuntime?.discord?.webhookUrl),
|
|
@@ -6373,31 +7890,31 @@ async function sendChannelMessage(request, config) {
|
|
|
6373
7890
|
async function runChannelsListCommand() {
|
|
6374
7891
|
const { root, statuses } = await getChannelRuntimeStatuses();
|
|
6375
7892
|
console.log("");
|
|
6376
|
-
console.log(
|
|
7893
|
+
console.log(chalk23.cyan.bold("Channel Runtime"));
|
|
6377
7894
|
if (!root) {
|
|
6378
|
-
console.log(
|
|
6379
|
-
console.log(
|
|
7895
|
+
console.log(chalk23.yellow("No synced upstream extensions found."));
|
|
7896
|
+
console.log(chalk23.gray("Run: pnpm features:sync"));
|
|
6380
7897
|
console.log("");
|
|
6381
7898
|
return;
|
|
6382
7899
|
}
|
|
6383
|
-
console.log(
|
|
7900
|
+
console.log(chalk23.gray(` Source: ${root}`));
|
|
6384
7901
|
console.log("");
|
|
6385
7902
|
if (statuses.length === 0) {
|
|
6386
|
-
console.log(
|
|
7903
|
+
console.log(chalk23.yellow("No channel manifests discovered."));
|
|
6387
7904
|
console.log("");
|
|
6388
7905
|
return;
|
|
6389
7906
|
}
|
|
6390
7907
|
const executable = statuses.filter((s) => s.executable).length;
|
|
6391
7908
|
const nonExecutable = statuses.length - executable;
|
|
6392
|
-
console.log(
|
|
6393
|
-
console.log(
|
|
6394
|
-
console.log(
|
|
7909
|
+
console.log(chalk23.white(`Channels discovered: ${statuses.length}`));
|
|
7910
|
+
console.log(chalk23.green(`Executable now: ${executable}`));
|
|
7911
|
+
console.log(chalk23.gray(`Manifest-only: ${nonExecutable}`));
|
|
6395
7912
|
console.log("");
|
|
6396
7913
|
for (const status of statuses) {
|
|
6397
|
-
const marker = status.executable ?
|
|
6398
|
-
const reason = status.reason ?
|
|
7914
|
+
const marker = status.executable ? chalk23.green("ready") : chalk23.yellow("manifest");
|
|
7915
|
+
const reason = status.reason ? chalk23.gray(` (${status.reason})`) : "";
|
|
6399
7916
|
console.log(
|
|
6400
|
-
`${marker} ${status.channelId} ${
|
|
7917
|
+
`${marker} ${status.channelId} ${chalk23.gray(`[${status.extensionId}]`)}${reason}`
|
|
6401
7918
|
);
|
|
6402
7919
|
}
|
|
6403
7920
|
console.log("");
|
|
@@ -6421,37 +7938,37 @@ async function runChannelsSendCommand(options) {
|
|
|
6421
7938
|
config
|
|
6422
7939
|
);
|
|
6423
7940
|
console.log("");
|
|
6424
|
-
console.log(
|
|
6425
|
-
console.log(
|
|
6426
|
-
console.log(
|
|
6427
|
-
console.log(
|
|
7941
|
+
console.log(chalk23.green.bold("Channel Message Sent"));
|
|
7942
|
+
console.log(chalk23.gray(` Channel: ${result.channel}`));
|
|
7943
|
+
console.log(chalk23.gray(` Extension: ${result.extensionId}`));
|
|
7944
|
+
console.log(chalk23.gray(` Detail: ${result.detail}`));
|
|
6428
7945
|
if (typeof result.responseStatus === "number") {
|
|
6429
|
-
console.log(
|
|
7946
|
+
console.log(chalk23.gray(` HTTP status: ${result.responseStatus}`));
|
|
6430
7947
|
}
|
|
6431
7948
|
console.log("");
|
|
6432
7949
|
}
|
|
6433
7950
|
function renderLevel(level) {
|
|
6434
7951
|
if (level === "ok") {
|
|
6435
|
-
return
|
|
7952
|
+
return chalk23.green("ok");
|
|
6436
7953
|
}
|
|
6437
7954
|
if (level === "warn") {
|
|
6438
|
-
return
|
|
7955
|
+
return chalk23.yellow("warn");
|
|
6439
7956
|
}
|
|
6440
|
-
return
|
|
7957
|
+
return chalk23.red("error");
|
|
6441
7958
|
}
|
|
6442
7959
|
async function runChannelsDoctorCommand() {
|
|
6443
7960
|
const config = loadConfig();
|
|
6444
7961
|
const runtime = resolveChannelRuntimeConfig(config);
|
|
6445
7962
|
const { root, statuses } = await getChannelRuntimeStatuses();
|
|
6446
7963
|
console.log("");
|
|
6447
|
-
console.log(
|
|
7964
|
+
console.log(chalk23.cyan.bold("Channel Doctor"));
|
|
6448
7965
|
if (!root) {
|
|
6449
|
-
console.log(
|
|
6450
|
-
console.log(
|
|
7966
|
+
console.log(chalk23.red("upstream/agdi/extensions not found."));
|
|
7967
|
+
console.log(chalk23.gray("Run: pnpm features:sync"));
|
|
6451
7968
|
console.log("");
|
|
6452
7969
|
return;
|
|
6453
7970
|
}
|
|
6454
|
-
console.log(
|
|
7971
|
+
console.log(chalk23.gray(` Source: ${root}`));
|
|
6455
7972
|
console.log("");
|
|
6456
7973
|
const executable = /* @__PURE__ */ new Map();
|
|
6457
7974
|
for (const status of statuses) {
|
|
@@ -6461,7 +7978,7 @@ async function runChannelsDoctorCommand() {
|
|
|
6461
7978
|
executable.set(status.channelId, { extensionId: status.extensionId });
|
|
6462
7979
|
}
|
|
6463
7980
|
if (executable.size === 0) {
|
|
6464
|
-
console.log(
|
|
7981
|
+
console.log(chalk23.yellow("No executable channels found."));
|
|
6465
7982
|
console.log("");
|
|
6466
7983
|
return;
|
|
6467
7984
|
}
|
|
@@ -6524,8 +8041,8 @@ async function runChannelsDoctorCommand() {
|
|
|
6524
8041
|
continue;
|
|
6525
8042
|
}
|
|
6526
8043
|
if (channelId === "whatsapp") {
|
|
6527
|
-
const credsPath =
|
|
6528
|
-
const hasCreds = await
|
|
8044
|
+
const credsPath = path18.join(runtime.whatsappAuthDir, "creds.json");
|
|
8045
|
+
const hasCreds = await fs16.pathExists(credsPath);
|
|
6529
8046
|
if (!hasCreds && !runtime.whatsappPrintQr) {
|
|
6530
8047
|
items.push({
|
|
6531
8048
|
channelId,
|
|
@@ -6571,20 +8088,20 @@ async function runChannelsDoctorCommand() {
|
|
|
6571
8088
|
const errorCount = items.filter((item) => item.level === "error").length;
|
|
6572
8089
|
for (const item of items) {
|
|
6573
8090
|
console.log(
|
|
6574
|
-
`${renderLevel(item.level)} ${item.channelId} ${
|
|
8091
|
+
`${renderLevel(item.level)} ${item.channelId} ${chalk23.gray(`[${item.extensionId}]`)} ${chalk23.gray("-")} ${item.detail}`
|
|
6575
8092
|
);
|
|
6576
8093
|
}
|
|
6577
8094
|
console.log("");
|
|
6578
|
-
console.log(
|
|
6579
|
-
console.log(
|
|
6580
|
-
console.log(
|
|
6581
|
-
console.log(
|
|
8095
|
+
console.log(chalk23.white(`Checked: ${items.length}`));
|
|
8096
|
+
console.log(chalk23.green(`OK: ${okCount}`));
|
|
8097
|
+
console.log(chalk23.yellow(`Warnings: ${warnCount}`));
|
|
8098
|
+
console.log(chalk23.red(`Errors: ${errorCount}`));
|
|
6582
8099
|
console.log("");
|
|
6583
8100
|
}
|
|
6584
8101
|
|
|
6585
8102
|
// src/commands/gateway.ts
|
|
6586
8103
|
import net from "net";
|
|
6587
|
-
import
|
|
8104
|
+
import chalk24 from "chalk";
|
|
6588
8105
|
function resolveGatewayTarget(options) {
|
|
6589
8106
|
const host = options.host?.trim() || process.env.AGDI_GATEWAY_HOST || "127.0.0.1";
|
|
6590
8107
|
const rawPort = options.port ?? process.env.AGDI_GATEWAY_PORT ?? "18789";
|
|
@@ -6628,17 +8145,17 @@ async function runGatewayStatusCommand(options) {
|
|
|
6628
8145
|
const open = await checkPortOpen(target);
|
|
6629
8146
|
const health = open ? await fetchGatewayHealth(target) : { ok: false };
|
|
6630
8147
|
console.log("");
|
|
6631
|
-
console.log(
|
|
6632
|
-
console.log(
|
|
6633
|
-
console.log(
|
|
8148
|
+
console.log(chalk24.cyan.bold("Gateway Status (native Agdi-dev)"));
|
|
8149
|
+
console.log(chalk24.gray(` Target: ws://${target.host}:${target.port}`));
|
|
8150
|
+
console.log(chalk24.gray(` Port open: ${open ? "yes" : "no"}`));
|
|
6634
8151
|
if (health.status !== void 0) {
|
|
6635
|
-
console.log(
|
|
8152
|
+
console.log(chalk24.gray(` Health HTTP: ${health.status}`));
|
|
6636
8153
|
} else {
|
|
6637
|
-
console.log(
|
|
8154
|
+
console.log(chalk24.gray(" Health HTTP: unreachable"));
|
|
6638
8155
|
}
|
|
6639
8156
|
if (health.body && health.body.trim().length > 0) {
|
|
6640
8157
|
const trimmed = health.body.length > 240 ? `${health.body.slice(0, 240)}...` : health.body;
|
|
6641
|
-
console.log(
|
|
8158
|
+
console.log(chalk24.gray(` Health body: ${trimmed}`));
|
|
6642
8159
|
}
|
|
6643
8160
|
console.log("");
|
|
6644
8161
|
}
|
|
@@ -6647,55 +8164,55 @@ async function runGatewayHealthCommand(options) {
|
|
|
6647
8164
|
const health = await fetchGatewayHealth(target);
|
|
6648
8165
|
if (!health.ok) {
|
|
6649
8166
|
console.log("");
|
|
6650
|
-
console.log(
|
|
6651
|
-
console.log(
|
|
8167
|
+
console.log(chalk24.red("Gateway health check failed."));
|
|
8168
|
+
console.log(chalk24.gray(` Target: http://${target.host}:${target.port}/health`));
|
|
6652
8169
|
if (health.status !== void 0) {
|
|
6653
|
-
console.log(
|
|
8170
|
+
console.log(chalk24.gray(` Status: ${health.status}`));
|
|
6654
8171
|
}
|
|
6655
8172
|
console.log("");
|
|
6656
8173
|
process.exitCode = 1;
|
|
6657
8174
|
return;
|
|
6658
8175
|
}
|
|
6659
8176
|
console.log("");
|
|
6660
|
-
console.log(
|
|
6661
|
-
console.log(
|
|
6662
|
-
console.log(
|
|
8177
|
+
console.log(chalk24.green.bold("Gateway healthy"));
|
|
8178
|
+
console.log(chalk24.gray(` Target: http://${target.host}:${target.port}/health`));
|
|
8179
|
+
console.log(chalk24.gray(` Status: ${health.status}`));
|
|
6663
8180
|
if (health.body && health.body.trim().length > 0) {
|
|
6664
8181
|
const trimmed = health.body.length > 240 ? `${health.body.slice(0, 240)}...` : health.body;
|
|
6665
|
-
console.log(
|
|
8182
|
+
console.log(chalk24.gray(` Body: ${trimmed}`));
|
|
6666
8183
|
}
|
|
6667
8184
|
console.log("");
|
|
6668
8185
|
}
|
|
6669
8186
|
|
|
6670
8187
|
// src/commands/upstream-proxy.ts
|
|
6671
8188
|
import { spawn as spawn2 } from "child_process";
|
|
6672
|
-
import
|
|
6673
|
-
import
|
|
6674
|
-
import
|
|
8189
|
+
import path19 from "path";
|
|
8190
|
+
import fs17 from "fs";
|
|
8191
|
+
import chalk25 from "chalk";
|
|
6675
8192
|
var DEFAULT_UPSTREAM_CANDIDATES = (cwd) => {
|
|
6676
8193
|
const envRepo = process.env.AGDI_UPSTREAM_REPO?.trim();
|
|
6677
8194
|
const envBin = process.env.AGDI_UPSTREAM_BIN?.trim();
|
|
6678
8195
|
const candidates = /* @__PURE__ */ new Set();
|
|
6679
8196
|
if (envBin) {
|
|
6680
|
-
candidates.add(
|
|
8197
|
+
candidates.add(path19.resolve(cwd, envBin));
|
|
6681
8198
|
}
|
|
6682
8199
|
if (envRepo) {
|
|
6683
|
-
candidates.add(
|
|
6684
|
-
}
|
|
6685
|
-
candidates.add(
|
|
6686
|
-
candidates.add(
|
|
6687
|
-
candidates.add(
|
|
6688
|
-
candidates.add(
|
|
6689
|
-
candidates.add(
|
|
6690
|
-
candidates.add(
|
|
6691
|
-
candidates.add(
|
|
6692
|
-
candidates.add(
|
|
6693
|
-
candidates.add(
|
|
8200
|
+
candidates.add(path19.resolve(cwd, envRepo, "agdi.mjs"));
|
|
8201
|
+
}
|
|
8202
|
+
candidates.add(path19.resolve(cwd, "upstream/agdi/agdi.mjs"));
|
|
8203
|
+
candidates.add(path19.resolve(cwd, "../upstream/agdi/agdi.mjs"));
|
|
8204
|
+
candidates.add(path19.resolve(cwd, "../../upstream/agdi/agdi.mjs"));
|
|
8205
|
+
candidates.add(path19.resolve(cwd, "../../../upstream/agdi/agdi.mjs"));
|
|
8206
|
+
candidates.add(path19.resolve(cwd, "../Agdi/agdi.mjs"));
|
|
8207
|
+
candidates.add(path19.resolve(cwd, "Agdi/agdi.mjs"));
|
|
8208
|
+
candidates.add(path19.resolve(cwd, "../../Agdi/agdi.mjs"));
|
|
8209
|
+
candidates.add(path19.resolve(cwd, "../../../Agdi/agdi.mjs"));
|
|
8210
|
+
candidates.add(path19.resolve(cwd, "../../../../Agdi/agdi.mjs"));
|
|
6694
8211
|
return [...candidates];
|
|
6695
8212
|
};
|
|
6696
8213
|
function resolveUpstreamBin(cwd = process.cwd()) {
|
|
6697
8214
|
for (const candidate of DEFAULT_UPSTREAM_CANDIDATES(cwd)) {
|
|
6698
|
-
if (
|
|
8215
|
+
if (fs17.existsSync(candidate)) {
|
|
6699
8216
|
return candidate;
|
|
6700
8217
|
}
|
|
6701
8218
|
}
|
|
@@ -6719,12 +8236,12 @@ async function runUpstreamProxy(spec) {
|
|
|
6719
8236
|
const upstreamBin = resolveUpstreamBin(process.cwd());
|
|
6720
8237
|
if (!upstreamBin) {
|
|
6721
8238
|
console.log("");
|
|
6722
|
-
console.log(
|
|
6723
|
-
console.log(
|
|
8239
|
+
console.log(chalk25.red("Upstream Agdi CLI not found."));
|
|
8240
|
+
console.log(chalk25.gray("Expected one of:"));
|
|
6724
8241
|
for (const candidate of DEFAULT_UPSTREAM_CANDIDATES(process.cwd())) {
|
|
6725
|
-
console.log(
|
|
8242
|
+
console.log(chalk25.gray(` - ${candidate}`));
|
|
6726
8243
|
}
|
|
6727
|
-
console.log(
|
|
8244
|
+
console.log(chalk25.gray("Set AGDI_UPSTREAM_REPO or AGDI_UPSTREAM_BIN to override."));
|
|
6728
8245
|
console.log("");
|
|
6729
8246
|
process.exitCode = 1;
|
|
6730
8247
|
return;
|
|
@@ -6737,7 +8254,7 @@ async function runUpstreamProxy(spec) {
|
|
|
6737
8254
|
env: process.env
|
|
6738
8255
|
});
|
|
6739
8256
|
child.on("error", (error) => {
|
|
6740
|
-
console.log(
|
|
8257
|
+
console.log(chalk25.red(`Failed to run upstream command: ${error.message}`));
|
|
6741
8258
|
process.exitCode = 1;
|
|
6742
8259
|
resolve3();
|
|
6743
8260
|
});
|
|
@@ -6745,7 +8262,7 @@ async function runUpstreamProxy(spec) {
|
|
|
6745
8262
|
if (typeof code === "number") {
|
|
6746
8263
|
process.exitCode = code;
|
|
6747
8264
|
} else if (signal) {
|
|
6748
|
-
console.log(
|
|
8265
|
+
console.log(chalk25.red(`Upstream command terminated by signal: ${signal}`));
|
|
6749
8266
|
process.exitCode = 1;
|
|
6750
8267
|
}
|
|
6751
8268
|
resolve3();
|
|
@@ -6760,7 +8277,6 @@ var UPSTREAM_PROXY_COMMANDS = [
|
|
|
6760
8277
|
{ name: "reset", description: "Reset upstream local state/config" },
|
|
6761
8278
|
{ name: "uninstall", description: "Uninstall upstream local service/data" },
|
|
6762
8279
|
{ name: "message", description: "Upstream message operations (send/read/manage)" },
|
|
6763
|
-
{ name: "memory", description: "Upstream memory commands" },
|
|
6764
8280
|
{ name: "agent", aliases: ["agents"], description: "Upstream agent management commands" },
|
|
6765
8281
|
{ name: "status", description: "Upstream gateway status" },
|
|
6766
8282
|
{ name: "health", description: "Upstream health checks" },
|
|
@@ -6776,7 +8292,6 @@ var UPSTREAM_PROXY_COMMANDS = [
|
|
|
6776
8292
|
{ name: "nodes", description: "Upstream node commands" },
|
|
6777
8293
|
{ name: "devices", description: "Upstream device pairing + token management" },
|
|
6778
8294
|
{ name: "node", description: "Upstream node control" },
|
|
6779
|
-
{ name: "sandbox", description: "Upstream sandbox tooling" },
|
|
6780
8295
|
{ name: "tui", description: "Upstream terminal UI" },
|
|
6781
8296
|
{ name: "cron", description: "Upstream cron scheduler" },
|
|
6782
8297
|
{ name: "dns", description: "Upstream DNS helpers" },
|
|
@@ -6784,7 +8299,6 @@ var UPSTREAM_PROXY_COMMANDS = [
|
|
|
6784
8299
|
{ name: "hooks", description: "Upstream hooks tooling" },
|
|
6785
8300
|
{ name: "webhooks", description: "Upstream webhook helpers" },
|
|
6786
8301
|
{ name: "pairing", description: "Upstream pairing helpers" },
|
|
6787
|
-
{ name: "plugins", description: "Upstream plugin management" },
|
|
6788
8302
|
{ name: "directory", description: "Upstream directory commands" },
|
|
6789
8303
|
{ name: "security", description: "Upstream security helpers" },
|
|
6790
8304
|
{ name: "skills", description: "Upstream skills management" },
|
|
@@ -6807,7 +8321,7 @@ function visibleWidth(input6) {
|
|
|
6807
8321
|
}
|
|
6808
8322
|
|
|
6809
8323
|
// src/cli/theme.ts
|
|
6810
|
-
import
|
|
8324
|
+
import chalk26, { Chalk } from "chalk";
|
|
6811
8325
|
var LOBSTER_PALETTE = {
|
|
6812
8326
|
accent: "#00d5ff",
|
|
6813
8327
|
accentBright: "#38bdf8",
|
|
@@ -6821,7 +8335,7 @@ var LOBSTER_PALETTE = {
|
|
|
6821
8335
|
var hasForceColor = typeof process.env.FORCE_COLOR === "string" && process.env.FORCE_COLOR.trim().length > 0 && process.env.FORCE_COLOR.trim() !== "0";
|
|
6822
8336
|
var hasNoColorArg = process.argv.includes("--no-color");
|
|
6823
8337
|
var disableColor = (process.env.NO_COLOR || hasNoColorArg) && !hasForceColor;
|
|
6824
|
-
var baseChalk = disableColor ? new Chalk({ level: 0 }) :
|
|
8338
|
+
var baseChalk = disableColor ? new Chalk({ level: 0 }) : chalk26;
|
|
6825
8339
|
var hex = (value) => baseChalk.hex(value);
|
|
6826
8340
|
var theme = {
|
|
6827
8341
|
accent: hex(LOBSTER_PALETTE.accent),
|
|
@@ -7091,8 +8605,8 @@ function formatTerminalLink(label, url, opts) {
|
|
|
7091
8605
|
}
|
|
7092
8606
|
return `\x1B]8;;${safeUrl}\x07${safeLabel}\x1B]8;;\x07`;
|
|
7093
8607
|
}
|
|
7094
|
-
function formatDocsLink(
|
|
7095
|
-
const trimmed =
|
|
8608
|
+
function formatDocsLink(path20, label) {
|
|
8609
|
+
const trimmed = path20.trim();
|
|
7096
8610
|
const url = trimmed.startsWith("http") ? trimmed : `${DOCS_ROOT}${trimmed.startsWith("/") ? trimmed : `/${trimmed}`}`;
|
|
7097
8611
|
return formatTerminalLink(label ?? url, url, { fallback: url });
|
|
7098
8612
|
}
|
|
@@ -7183,7 +8697,7 @@ program.action(async () => {
|
|
|
7183
8697
|
await runWizard();
|
|
7184
8698
|
} catch (error) {
|
|
7185
8699
|
if (error.name === "ExitPromptError") {
|
|
7186
|
-
console.log(
|
|
8700
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Goodbye!\n"));
|
|
7187
8701
|
try {
|
|
7188
8702
|
ui.safeExit(0);
|
|
7189
8703
|
} catch {
|
|
@@ -7205,7 +8719,7 @@ program.command("auth").description("Configure API keys").option("--status", "Sh
|
|
|
7205
8719
|
}
|
|
7206
8720
|
} catch (error) {
|
|
7207
8721
|
if (error.name === "ExitPromptError") {
|
|
7208
|
-
console.log(
|
|
8722
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
7209
8723
|
try {
|
|
7210
8724
|
ui.safeExit(0);
|
|
7211
8725
|
} catch {
|
|
@@ -7223,7 +8737,7 @@ program.command("model").alias("models").description("Change AI model").action(a
|
|
|
7223
8737
|
await selectModel();
|
|
7224
8738
|
} catch (error) {
|
|
7225
8739
|
if (error.name === "ExitPromptError") {
|
|
7226
|
-
console.log(
|
|
8740
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
7227
8741
|
try {
|
|
7228
8742
|
ui.safeExit(0);
|
|
7229
8743
|
} catch {
|
|
@@ -7244,7 +8758,7 @@ program.command("chat").description("Start a chat session").action(async () => {
|
|
|
7244
8758
|
await startChat();
|
|
7245
8759
|
} catch (error) {
|
|
7246
8760
|
if (error.name === "ExitPromptError") {
|
|
7247
|
-
console.log(
|
|
8761
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Goodbye!\n"));
|
|
7248
8762
|
try {
|
|
7249
8763
|
ui.safeExit(0);
|
|
7250
8764
|
} catch {
|
|
@@ -7262,7 +8776,7 @@ program.command("run [directory]").description("Run a generated project").action
|
|
|
7262
8776
|
await runProject(directory);
|
|
7263
8777
|
} catch (error) {
|
|
7264
8778
|
if (error.name === "ExitPromptError") {
|
|
7265
|
-
console.log(
|
|
8779
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
7266
8780
|
try {
|
|
7267
8781
|
ui.safeExit(0);
|
|
7268
8782
|
} catch {
|
|
@@ -7285,10 +8799,10 @@ program.command("build <prompt>").alias("b").description("Generate an app from a
|
|
|
7285
8799
|
}
|
|
7286
8800
|
const activeConfig = getActiveProvider();
|
|
7287
8801
|
if (!activeConfig) {
|
|
7288
|
-
console.log(
|
|
8802
|
+
console.log(chalk27.red("\u274C No API key configured. Run: agdi auth"));
|
|
7289
8803
|
return;
|
|
7290
8804
|
}
|
|
7291
|
-
const spinner =
|
|
8805
|
+
const spinner = ora12("Generating application...").start();
|
|
7292
8806
|
try {
|
|
7293
8807
|
const llm = createLLMProvider(activeConfig.provider, {
|
|
7294
8808
|
apiKey: activeConfig.apiKey,
|
|
@@ -7297,52 +8811,52 @@ program.command("build <prompt>").alias("b").description("Generate an app from a
|
|
|
7297
8811
|
const pm = new ProjectManager();
|
|
7298
8812
|
pm.create(options.output.replace("./", ""), prompt);
|
|
7299
8813
|
const { plan, files } = await generateApp(prompt, llm, (step, file) => {
|
|
7300
|
-
spinner.text = file ? `${step} ${
|
|
8814
|
+
spinner.text = file ? `${step} ${chalk27.gray(file)}` : step;
|
|
7301
8815
|
});
|
|
7302
8816
|
pm.updateFiles(files);
|
|
7303
8817
|
pm.updateDependencies(plan.dependencies);
|
|
7304
8818
|
if (options.dryRun || ui.flags.dryRun) {
|
|
7305
8819
|
spinner.stop();
|
|
7306
|
-
console.log(
|
|
7307
|
-
console.log(
|
|
8820
|
+
console.log(chalk27.cyan.bold("\n\u{1F6A7} DRY RUN SUMMARY\n"));
|
|
8821
|
+
console.log(chalk27.gray(`Project: ${plan.name}
|
|
7308
8822
|
`));
|
|
7309
|
-
console.log(
|
|
7310
|
-
files.forEach((f) => console.log(
|
|
7311
|
-
console.log(
|
|
7312
|
-
console.log(
|
|
7313
|
-
console.log(
|
|
8823
|
+
console.log(chalk27.cyan("Files to be created:"));
|
|
8824
|
+
files.forEach((f) => console.log(chalk27.gray(` \u{1F4C4} ${f.path}`)));
|
|
8825
|
+
console.log(chalk27.cyan("\nDependencies:"));
|
|
8826
|
+
console.log(chalk27.gray(` \u{1F4E6} ${plan.dependencies.join(", ")}`));
|
|
8827
|
+
console.log(chalk27.green("\n\u2713 Dry run complete. No files written.\n"));
|
|
7314
8828
|
return;
|
|
7315
8829
|
}
|
|
7316
8830
|
await writeProject(pm.get(), options.output);
|
|
7317
|
-
spinner.succeed(
|
|
7318
|
-
console.log(
|
|
7319
|
-
\u{1F4C1} Created ${files.length} files in ${
|
|
8831
|
+
spinner.succeed(chalk27.green("App generated!"));
|
|
8832
|
+
console.log(chalk27.gray(`
|
|
8833
|
+
\u{1F4C1} Created ${files.length} files in ${chalk27.cyan(options.output)}`));
|
|
7320
8834
|
if (ui.flags.saas || options.saas) {
|
|
7321
|
-
console.log(
|
|
7322
|
-
console.log(
|
|
7323
|
-
console.log(
|
|
7324
|
-
console.log(
|
|
7325
|
-
console.log(
|
|
7326
|
-
console.log(
|
|
7327
|
-
console.log(
|
|
8835
|
+
console.log(chalk27.cyan("\nSaaS Quick Start:"));
|
|
8836
|
+
console.log(chalk27.gray(` 1) cd ${options.output}`));
|
|
8837
|
+
console.log(chalk27.gray(" 2) npm install"));
|
|
8838
|
+
console.log(chalk27.gray(" 3) cp .env.example .env"));
|
|
8839
|
+
console.log(chalk27.gray(" 4) npx prisma generate"));
|
|
8840
|
+
console.log(chalk27.gray(" 5) npx prisma db push"));
|
|
8841
|
+
console.log(chalk27.gray(" 6) npm run dev\n"));
|
|
7328
8842
|
} else {
|
|
7329
|
-
console.log(
|
|
8843
|
+
console.log(chalk27.gray("\nNext: cd " + options.output + " && npm install && npm run dev\n"));
|
|
7330
8844
|
}
|
|
7331
8845
|
} catch (error) {
|
|
7332
8846
|
spinner.fail("Generation failed");
|
|
7333
8847
|
const msg = error instanceof Error ? error.message : String(error);
|
|
7334
8848
|
if (msg.includes("429") || msg.includes("quota")) {
|
|
7335
|
-
console.log(
|
|
8849
|
+
console.log(chalk27.yellow("\n\u26A0\uFE0F Quota exceeded. Run: agdi model\n"));
|
|
7336
8850
|
} else if (msg.includes("401") || msg.includes("403")) {
|
|
7337
|
-
console.log(
|
|
8851
|
+
console.log(chalk27.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
|
|
7338
8852
|
} else {
|
|
7339
|
-
console.error(
|
|
8853
|
+
console.error(chalk27.red("\n" + msg + "\n"));
|
|
7340
8854
|
}
|
|
7341
8855
|
ui.safeExit(1);
|
|
7342
8856
|
}
|
|
7343
8857
|
} catch (error) {
|
|
7344
8858
|
if (error.name === "ExitPromptError") {
|
|
7345
|
-
console.log(
|
|
8859
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
7346
8860
|
try {
|
|
7347
8861
|
ui.safeExit(0);
|
|
7348
8862
|
} catch {
|
|
@@ -7358,11 +8872,11 @@ program.command("build <prompt>").alias("b").description("Generate an app from a
|
|
|
7358
8872
|
program.command("config").description("Show configuration").action(async () => {
|
|
7359
8873
|
const config = loadConfig();
|
|
7360
8874
|
const active = getActiveProvider();
|
|
7361
|
-
console.log(
|
|
7362
|
-
console.log(
|
|
7363
|
-
console.log(
|
|
7364
|
-
console.log(
|
|
7365
|
-
console.log(
|
|
8875
|
+
console.log(chalk27.cyan.bold("\n\u2699\uFE0F Configuration\n"));
|
|
8876
|
+
console.log(chalk27.gray(" Provider: ") + chalk27.cyan(config.defaultProvider || "not set"));
|
|
8877
|
+
console.log(chalk27.gray(" Model: ") + chalk27.cyan(config.defaultModel || "not set"));
|
|
8878
|
+
console.log(chalk27.gray(" Config: ") + chalk27.gray("~/.agdi/config.json"));
|
|
8879
|
+
console.log(chalk27.cyan.bold("\n\u{1F510} API Keys\n"));
|
|
7366
8880
|
const keys = [
|
|
7367
8881
|
["Gemini", config.geminiApiKey],
|
|
7368
8882
|
["OpenRouter", config.openrouterApiKey],
|
|
@@ -7371,13 +8885,13 @@ program.command("config").description("Show configuration").action(async () => {
|
|
|
7371
8885
|
["DeepSeek", config.deepseekApiKey]
|
|
7372
8886
|
];
|
|
7373
8887
|
for (const [name, key] of keys) {
|
|
7374
|
-
const status = key ?
|
|
8888
|
+
const status = key ? chalk27.green("\u2713") : chalk27.gray("\u2717");
|
|
7375
8889
|
console.log(` ${status} ${name}`);
|
|
7376
8890
|
}
|
|
7377
|
-
console.log(
|
|
8891
|
+
console.log(chalk27.cyan.bold("\n\u{1F4CA} Telemetry\n"));
|
|
7378
8892
|
const telemetryEnabled = config.telemetry?.enabled ?? false;
|
|
7379
|
-
console.log(` ${telemetryEnabled ?
|
|
7380
|
-
console.log(
|
|
8893
|
+
console.log(` ${telemetryEnabled ? chalk27.green("\u2713 Enabled") : chalk27.gray("\u2717 Disabled")}`);
|
|
8894
|
+
console.log(chalk27.gray(" Change with: agdi config telemetry --enable | --disable"));
|
|
7381
8895
|
console.log("");
|
|
7382
8896
|
console.log("");
|
|
7383
8897
|
});
|
|
@@ -7385,59 +8899,59 @@ program.command("config:telemetry").alias("telemetry").description("Manage telem
|
|
|
7385
8899
|
const { isTelemetryEnabled, setTelemetryConsent, getTelemetryConfig } = await import("./config-K2XM6D4Z.js");
|
|
7386
8900
|
const { generateSampleEvent, generateSanitizationDemo } = await import("./telemetry-service-76YPOPDM.js");
|
|
7387
8901
|
if (options.dryRun || options.test) {
|
|
7388
|
-
console.log(
|
|
7389
|
-
console.log(
|
|
7390
|
-
console.log(
|
|
7391
|
-
console.log(
|
|
8902
|
+
console.log(chalk27.cyan.bold("\n\u{1F50D} TELEMETRY TRANSPARENCY MODE\n"));
|
|
8903
|
+
console.log(chalk27.gray("This is exactly what Agdi sends. Notice there is"));
|
|
8904
|
+
console.log(chalk27.green.bold("NO source code, file paths, or API keys.\n"));
|
|
8905
|
+
console.log(chalk27.white.bold('\u{1F4CA} Sample "Build Failed" Event:\n'));
|
|
7392
8906
|
const sample = generateSampleEvent();
|
|
7393
|
-
console.log(
|
|
7394
|
-
console.log(
|
|
7395
|
-
console.log(
|
|
7396
|
-
console.log(
|
|
8907
|
+
console.log(chalk27.gray(JSON.stringify(sample, null, 2)));
|
|
8908
|
+
console.log(chalk27.white.bold("\n\u{1F6E1}\uFE0F Sanitization Demo:\n"));
|
|
8909
|
+
console.log(chalk27.gray("Even if sensitive data accidentally enters an error message,"));
|
|
8910
|
+
console.log(chalk27.gray("our sanitization layer strips it before transmission:\n"));
|
|
7397
8911
|
const demo = generateSanitizationDemo();
|
|
7398
|
-
console.log(
|
|
7399
|
-
console.log(
|
|
8912
|
+
console.log(chalk27.red.bold("BEFORE sanitization (never sent):"));
|
|
8913
|
+
console.log(chalk27.gray(JSON.stringify({
|
|
7400
8914
|
errorCode: demo.before.errorCode,
|
|
7401
8915
|
feedback: demo.before.feedback
|
|
7402
8916
|
}, null, 2)));
|
|
7403
|
-
console.log(
|
|
7404
|
-
console.log(
|
|
8917
|
+
console.log(chalk27.green.bold("\nAFTER sanitization (what we actually send):"));
|
|
8918
|
+
console.log(chalk27.gray(JSON.stringify({
|
|
7405
8919
|
errorCode: demo.after.errorCode,
|
|
7406
8920
|
feedback: demo.after.feedback
|
|
7407
8921
|
}, null, 2)));
|
|
7408
|
-
console.log(
|
|
7409
|
-
console.log(
|
|
8922
|
+
console.log(chalk27.cyan("\n\u2705 Your code and secrets are NEVER transmitted."));
|
|
8923
|
+
console.log(chalk27.gray(" Learn more: https://agdi-dev.vercel.app/privacy\n"));
|
|
7410
8924
|
return;
|
|
7411
8925
|
}
|
|
7412
8926
|
if (options.enable) {
|
|
7413
8927
|
setTelemetryConsent(true);
|
|
7414
|
-
console.log(
|
|
7415
|
-
console.log(
|
|
7416
|
-
console.log(
|
|
7417
|
-
console.log(
|
|
8928
|
+
console.log(chalk27.green("\n\u2705 Telemetry enabled"));
|
|
8929
|
+
console.log(chalk27.gray(" We collect: success/fail, error types, model used"));
|
|
8930
|
+
console.log(chalk27.gray(" We NEVER collect: source code, API keys, file paths"));
|
|
8931
|
+
console.log(chalk27.gray(" Verify anytime: agdi config telemetry --dry-run\n"));
|
|
7418
8932
|
} else if (options.disable) {
|
|
7419
8933
|
setTelemetryConsent(false);
|
|
7420
|
-
console.log(
|
|
7421
|
-
console.log(
|
|
8934
|
+
console.log(chalk27.yellow("\n\u{1F4CA} Telemetry disabled"));
|
|
8935
|
+
console.log(chalk27.gray(" You can re-enable anytime with: agdi config telemetry --enable\n"));
|
|
7422
8936
|
} else {
|
|
7423
8937
|
const config = getTelemetryConfig();
|
|
7424
|
-
console.log(
|
|
7425
|
-
console.log(
|
|
7426
|
-
console.log(
|
|
8938
|
+
console.log(chalk27.cyan.bold("\n\u{1F4CA} Telemetry Status\n"));
|
|
8939
|
+
console.log(chalk27.gray(" Enabled: ") + (config.enabled ? chalk27.green("Yes") : chalk27.gray("No")));
|
|
8940
|
+
console.log(chalk27.gray(" Consent: ") + (config.consentAsked ? chalk27.green("Asked") : chalk27.gray("Not asked")));
|
|
7427
8941
|
if (config.anonymousId) {
|
|
7428
|
-
console.log(
|
|
8942
|
+
console.log(chalk27.gray(" ID: ") + chalk27.gray(config.anonymousId.slice(0, 8) + "..."));
|
|
7429
8943
|
}
|
|
7430
8944
|
console.log("");
|
|
7431
|
-
console.log(
|
|
7432
|
-
console.log(
|
|
7433
|
-
console.log(
|
|
8945
|
+
console.log(chalk27.gray(" Enable: agdi config telemetry --enable"));
|
|
8946
|
+
console.log(chalk27.gray(" Disable: agdi config telemetry --disable"));
|
|
8947
|
+
console.log(chalk27.gray(" Verify: agdi config telemetry --dry-run\n"));
|
|
7434
8948
|
}
|
|
7435
8949
|
});
|
|
7436
8950
|
program.command("doctor").alias("doc").description("Run self-diagnosis checks").action(async () => {
|
|
7437
8951
|
try {
|
|
7438
8952
|
await runDoctor();
|
|
7439
8953
|
} catch (error) {
|
|
7440
|
-
console.error(
|
|
8954
|
+
console.error(chalk27.red("Diagnostic failed: " + error));
|
|
7441
8955
|
ui.safeExit(1);
|
|
7442
8956
|
}
|
|
7443
8957
|
});
|
|
@@ -7446,7 +8960,7 @@ program.command("features").description("List or sync upstream Agdi feature pack
|
|
|
7446
8960
|
await runFeaturesCommand(options);
|
|
7447
8961
|
} catch (error) {
|
|
7448
8962
|
if (error.name === "ExitPromptError") {
|
|
7449
|
-
console.log(
|
|
8963
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
7450
8964
|
try {
|
|
7451
8965
|
ui.safeExit(0);
|
|
7452
8966
|
} catch {
|
|
@@ -7465,7 +8979,7 @@ channelsCommand.command("list").description("List discovered channel extensions
|
|
|
7465
8979
|
await runChannelsListCommand();
|
|
7466
8980
|
} catch (error) {
|
|
7467
8981
|
if (error.name === "ExitPromptError") {
|
|
7468
|
-
console.log(
|
|
8982
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
7469
8983
|
try {
|
|
7470
8984
|
ui.safeExit(0);
|
|
7471
8985
|
} catch {
|
|
@@ -7483,7 +8997,7 @@ channelsCommand.command("send").description("Send a message through a supported
|
|
|
7483
8997
|
await runChannelsSendCommand(options);
|
|
7484
8998
|
} catch (error) {
|
|
7485
8999
|
const message = error instanceof Error ? error.message : String(error);
|
|
7486
|
-
console.log(
|
|
9000
|
+
console.log(chalk27.red(`
|
|
7487
9001
|
${message}
|
|
7488
9002
|
`));
|
|
7489
9003
|
try {
|
|
@@ -7497,7 +9011,7 @@ channelsCommand.command("doctor").description("Validate tokens/webhooks/session
|
|
|
7497
9011
|
await runChannelsDoctorCommand();
|
|
7498
9012
|
} catch (error) {
|
|
7499
9013
|
const message = error instanceof Error ? error.message : String(error);
|
|
7500
|
-
console.log(
|
|
9014
|
+
console.log(chalk27.red(`
|
|
7501
9015
|
${message}
|
|
7502
9016
|
`));
|
|
7503
9017
|
try {
|
|
@@ -7526,7 +9040,7 @@ function registerUpstreamProxyCommand(spec) {
|
|
|
7526
9040
|
if (requestedNames.some((name) => existingNames.has(name))) {
|
|
7527
9041
|
return;
|
|
7528
9042
|
}
|
|
7529
|
-
const command = program.command(`${spec.name} [args...]`).description(`${spec.description} ${
|
|
9043
|
+
const command = program.command(`${spec.name} [args...]`).description(`${spec.description} ${chalk27.gray("(proxied to upstream Agdi)")}`).helpOption(false).allowUnknownOption(true).allowExcessArguments(true).action(async () => {
|
|
7530
9044
|
await runUpstreamProxy(spec);
|
|
7531
9045
|
});
|
|
7532
9046
|
for (const alias of spec.aliases ?? []) {
|
|
@@ -7536,14 +9050,135 @@ function registerUpstreamProxyCommand(spec) {
|
|
|
7536
9050
|
for (const spec of UPSTREAM_PROXY_COMMANDS) {
|
|
7537
9051
|
registerUpstreamProxyCommand(spec);
|
|
7538
9052
|
}
|
|
7539
|
-
program.command("
|
|
9053
|
+
program.command("audit").description("\u{1F50D} Scan codebase for production-readiness issues").option("--prod", "Strict production mode (errors on a11y/security/test gaps)").option("--fix", "Auto-fix issues using the QA agent").option("-o, --output <dir>", "Directory to scan", "./").action(async (options) => {
|
|
9054
|
+
try {
|
|
9055
|
+
const result = await runAuditCommand({
|
|
9056
|
+
prod: options.prod,
|
|
9057
|
+
fix: options.fix,
|
|
9058
|
+
output: options.output
|
|
9059
|
+
});
|
|
9060
|
+
if (!result.passed && options.prod) {
|
|
9061
|
+
ui.safeExit(1);
|
|
9062
|
+
}
|
|
9063
|
+
} catch (error) {
|
|
9064
|
+
if (error.name === "ExitPromptError") {
|
|
9065
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
9066
|
+
try {
|
|
9067
|
+
ui.safeExit(0);
|
|
9068
|
+
} catch {
|
|
9069
|
+
}
|
|
9070
|
+
return;
|
|
9071
|
+
}
|
|
9072
|
+
throw error;
|
|
9073
|
+
}
|
|
9074
|
+
});
|
|
9075
|
+
program.command("sandbox <url>").description("\u{1F9EA} Read live API docs and extract endpoints").option("--test", "Ping discovered endpoints to verify response shape").option("--inject", "Prepare endpoint summary for Squad context injection").option("--api-key <key>", "API key for authenticated endpoints").action(async (url, options) => {
|
|
9076
|
+
try {
|
|
9077
|
+
await runSandboxCommand(url, {
|
|
9078
|
+
test: options.test,
|
|
9079
|
+
inject: options.inject,
|
|
9080
|
+
apiKey: options.apiKey
|
|
9081
|
+
});
|
|
9082
|
+
} catch (error) {
|
|
9083
|
+
if (error.name === "ExitPromptError") {
|
|
9084
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
9085
|
+
try {
|
|
9086
|
+
ui.safeExit(0);
|
|
9087
|
+
} catch {
|
|
9088
|
+
}
|
|
9089
|
+
return;
|
|
9090
|
+
}
|
|
9091
|
+
throw error;
|
|
9092
|
+
}
|
|
9093
|
+
});
|
|
9094
|
+
program.command("visual-test").alias("vt").description("\u{1F4F8} Run visual regression tests on your app").option("--routes <routes>", "Comma-separated routes to test (default: auto-discover)").option("--update", "Update baseline screenshots").option("--port <port>", "Dev server port", "3000").option("-o, --output <dir>", "Workspace directory", "./").action(async (options) => {
|
|
9095
|
+
try {
|
|
9096
|
+
await runVisualTestCommand({
|
|
9097
|
+
routes: options.routes,
|
|
9098
|
+
update: options.update,
|
|
9099
|
+
port: parseInt(options.port, 10),
|
|
9100
|
+
output: options.output
|
|
9101
|
+
});
|
|
9102
|
+
} catch (error) {
|
|
9103
|
+
if (error.name === "ExitPromptError") {
|
|
9104
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
9105
|
+
try {
|
|
9106
|
+
ui.safeExit(0);
|
|
9107
|
+
} catch {
|
|
9108
|
+
}
|
|
9109
|
+
return;
|
|
9110
|
+
}
|
|
9111
|
+
throw error;
|
|
9112
|
+
}
|
|
9113
|
+
});
|
|
9114
|
+
program.command("memory").alias("mem").description("\u{1F9E0} Analyze git history for project context").option("--depth <n>", "Number of commits to analyze", "30").option("--inject", "Prepare context for Squad injection").option("-o, --output <dir>", "Workspace directory", "./").action(async (options) => {
|
|
9115
|
+
try {
|
|
9116
|
+
await runMemoryCommand({
|
|
9117
|
+
depth: parseInt(options.depth, 10),
|
|
9118
|
+
inject: options.inject,
|
|
9119
|
+
output: options.output
|
|
9120
|
+
});
|
|
9121
|
+
} catch (error) {
|
|
9122
|
+
if (error.name === "ExitPromptError") {
|
|
9123
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
9124
|
+
try {
|
|
9125
|
+
ui.safeExit(0);
|
|
9126
|
+
} catch {
|
|
9127
|
+
}
|
|
9128
|
+
return;
|
|
9129
|
+
}
|
|
9130
|
+
throw error;
|
|
9131
|
+
}
|
|
9132
|
+
});
|
|
9133
|
+
program.command("deps").description("\u{1F6E1}\uFE0F Scan dependencies for vulnerabilities").option("--full", "Full audit including outdated packages").option("--fix", "Auto-fix vulnerabilities with npm audit fix").option("-o, --output <dir>", "Workspace directory", "./").action(async (options) => {
|
|
9134
|
+
try {
|
|
9135
|
+
const result = await runDepsScanCommand({
|
|
9136
|
+
full: options.full,
|
|
9137
|
+
fix: options.fix,
|
|
9138
|
+
output: options.output
|
|
9139
|
+
});
|
|
9140
|
+
if (!result.passed) {
|
|
9141
|
+
ui.safeExit(1);
|
|
9142
|
+
}
|
|
9143
|
+
} catch (error) {
|
|
9144
|
+
if (error.name === "ExitPromptError") {
|
|
9145
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
9146
|
+
try {
|
|
9147
|
+
ui.safeExit(0);
|
|
9148
|
+
} catch {
|
|
9149
|
+
}
|
|
9150
|
+
return;
|
|
9151
|
+
}
|
|
9152
|
+
throw error;
|
|
9153
|
+
}
|
|
9154
|
+
});
|
|
9155
|
+
program.command("plugins").alias("plug").description("\u{1F50C} Manage Agdi plugins and extensions").option("--install <name>", "Install a plugin from npm").option("--init", "Create a new plugin template").option("-o, --output <dir>", "Workspace directory", "./").action(async (options) => {
|
|
9156
|
+
try {
|
|
9157
|
+
await runPluginsCommand({
|
|
9158
|
+
install: options.install,
|
|
9159
|
+
init: options.init,
|
|
9160
|
+
output: options.output
|
|
9161
|
+
});
|
|
9162
|
+
} catch (error) {
|
|
9163
|
+
if (error.name === "ExitPromptError") {
|
|
9164
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
9165
|
+
try {
|
|
9166
|
+
ui.safeExit(0);
|
|
9167
|
+
} catch {
|
|
9168
|
+
}
|
|
9169
|
+
return;
|
|
9170
|
+
}
|
|
9171
|
+
throw error;
|
|
9172
|
+
}
|
|
9173
|
+
});
|
|
9174
|
+
program.command("squad [prompt]").alias("s").description("\u{1F9B8} Autonomous multi-agent app builder").option("-d, --deploy", "Auto-deploy to Vercel after build").option("-o, --output <dir>", "Output directory", "./").option("-v, --verbose", "Show detailed agent logs", true).option("--breakpoints", "Pause after each agent task for review (AI Breakpoints)").action(async (prompt, options) => {
|
|
7540
9175
|
try {
|
|
7541
9176
|
if (needsOnboarding()) {
|
|
7542
9177
|
await runOnboarding();
|
|
7543
9178
|
}
|
|
7544
9179
|
const activeConfig = getActiveProvider();
|
|
7545
9180
|
if (!activeConfig) {
|
|
7546
|
-
console.log(
|
|
9181
|
+
console.log(chalk27.red("\u274C No API key configured. Run: agdi auth"));
|
|
7547
9182
|
return;
|
|
7548
9183
|
}
|
|
7549
9184
|
const llm = createLLMProvider(activeConfig.provider, {
|
|
@@ -7553,11 +9188,12 @@ program.command("squad [prompt]").alias("s").description("\u{1F9B8} Autonomous m
|
|
|
7553
9188
|
await runSquadCommand(prompt, llm, {
|
|
7554
9189
|
deploy: options.deploy,
|
|
7555
9190
|
output: options.output,
|
|
7556
|
-
verbose: options.verbose
|
|
9191
|
+
verbose: options.verbose,
|
|
9192
|
+
breakpoints: options.breakpoints
|
|
7557
9193
|
});
|
|
7558
9194
|
} catch (error) {
|
|
7559
9195
|
if (error.name === "ExitPromptError") {
|
|
7560
|
-
console.log(
|
|
9196
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
7561
9197
|
try {
|
|
7562
9198
|
ui.safeExit(0);
|
|
7563
9199
|
} catch {
|
|
@@ -7585,7 +9221,7 @@ program.command("replay <runId>").description("\u{1F501} Replay a previous squad
|
|
|
7585
9221
|
}
|
|
7586
9222
|
const activeConfig = getActiveProvider();
|
|
7587
9223
|
if (!activeConfig) {
|
|
7588
|
-
console.log(
|
|
9224
|
+
console.log(chalk27.red("\u274C No API key configured. Run: agdi auth"));
|
|
7589
9225
|
return;
|
|
7590
9226
|
}
|
|
7591
9227
|
const llm = createLLMProvider(activeConfig.provider, {
|
|
@@ -7599,7 +9235,7 @@ program.command("replay <runId>").description("\u{1F501} Replay a previous squad
|
|
|
7599
9235
|
});
|
|
7600
9236
|
} catch (error) {
|
|
7601
9237
|
if (error.name === "ExitPromptError") {
|
|
7602
|
-
console.log(
|
|
9238
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
7603
9239
|
try {
|
|
7604
9240
|
ui.safeExit(0);
|
|
7605
9241
|
} catch {
|
|
@@ -7617,7 +9253,7 @@ program.command("import <url>").alias("i").description("\u{1F4E6} Import a GitHu
|
|
|
7617
9253
|
await runImportCommand(url, options.output);
|
|
7618
9254
|
} catch (error) {
|
|
7619
9255
|
if (error.name === "ExitPromptError") {
|
|
7620
|
-
console.log(
|
|
9256
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
7621
9257
|
try {
|
|
7622
9258
|
ui.safeExit(0);
|
|
7623
9259
|
} catch {
|
|
@@ -7631,7 +9267,7 @@ program.command("wizard").alias("w").description("\u{1F9D9} Start the Agdi Setup
|
|
|
7631
9267
|
try {
|
|
7632
9268
|
await runWizard();
|
|
7633
9269
|
} catch (error) {
|
|
7634
|
-
console.log(
|
|
9270
|
+
console.log(chalk27.gray("\n\n\u{1F44B} Cancelled.\n"));
|
|
7635
9271
|
try {
|
|
7636
9272
|
ui.safeExit(0);
|
|
7637
9273
|
} catch {
|