@aigne/doc-smith 0.9.6-beta.1 ā 0.9.6
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/CHANGELOG.md +10 -0
- package/agents/create/document-structure-tools/delete-document.mjs +21 -0
- package/agents/create/user-add-document/print-add-document-summary.mjs +12 -5
- package/agents/create/user-add-document/review-documents-with-new-links.mjs +45 -8
- package/agents/create/user-remove-document/find-documents-with-invalid-links.mjs +11 -0
- package/agents/create/user-remove-document/print-remove-document-summary.mjs +2 -5
- package/agents/create/user-remove-document/remove-documents-from-structure.mjs +35 -32
- package/agents/create/user-remove-document/review-documents-with-invalid-links.mjs +11 -8
- package/agents/init/index.mjs +3 -4
- package/agents/utils/load-sources.mjs +36 -46
- package/aigne.yaml +1 -1
- package/package.json +1 -1
- package/prompts/detail/custom/custom-components/x-cards-usage-rules.md +18 -10
- package/utils/docs-finder-utils.mjs +79 -0
- package/utils/file-utils.mjs +9 -7
- package/utils/load-config.mjs +21 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.6](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.9.6-beta.2...v0.9.6) (2025-11-21)
|
|
4
|
+
|
|
5
|
+
## [0.9.6-beta.2](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.9.6-beta.1...v0.9.6-beta.2) (2025-11-20)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* enhance the robustness of add/remove document ([#325](https://github.com/AIGNE-io/aigne-doc-smith/issues/325)) ([f78668a](https://github.com/AIGNE-io/aigne-doc-smith/commit/f78668ad93fb1c9913cfd62de466018f177990b3))
|
|
11
|
+
* ignore video media temporary ([#326](https://github.com/AIGNE-io/aigne-doc-smith/issues/326)) ([e65e7fd](https://github.com/AIGNE-io/aigne-doc-smith/commit/e65e7fda49d870c1e60b433fac121193ee72a328))
|
|
12
|
+
|
|
3
13
|
## [0.9.6-beta.1](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.9.6-beta...v0.9.6-beta.1) (2025-11-19)
|
|
4
14
|
|
|
5
15
|
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
getDeleteDocumentOutputJsonSchema,
|
|
4
4
|
validateDeleteDocumentInput,
|
|
5
5
|
} from "../../../types/document-structure-schema.mjs";
|
|
6
|
+
import { userContextAt } from "../../../utils/utils.mjs";
|
|
6
7
|
|
|
7
8
|
export default async function deleteDocument(input, options) {
|
|
8
9
|
// Validate input using Zod schema
|
|
@@ -23,6 +24,21 @@ export default async function deleteDocument(input, options) {
|
|
|
23
24
|
documentStructure = input.documentStructure;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
const deletedPathsContext = userContextAt(options, "deletedPaths");
|
|
28
|
+
const deletedPaths = deletedPathsContext.get() || [];
|
|
29
|
+
|
|
30
|
+
// Check if path has already been deleted
|
|
31
|
+
if (recursive) {
|
|
32
|
+
if (deletedPaths.includes(path)) {
|
|
33
|
+
const message = `Skipping duplicate deletion. Document '${path}' has already been deleted.`;
|
|
34
|
+
return {
|
|
35
|
+
documentStructure,
|
|
36
|
+
message,
|
|
37
|
+
deletedDocuments: [],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
26
42
|
// Find the document to delete
|
|
27
43
|
const documentIndex = documentStructure.findIndex((item) => item.path === path);
|
|
28
44
|
if (documentIndex === -1) {
|
|
@@ -72,6 +88,11 @@ export default async function deleteDocument(input, options) {
|
|
|
72
88
|
// Remove all documents from the structure
|
|
73
89
|
const updatedStructure = documentStructure.filter((item) => !pathsToDelete.has(item.path));
|
|
74
90
|
|
|
91
|
+
// Add paths to deleted paths
|
|
92
|
+
if (recursive) {
|
|
93
|
+
deletedPathsContext.set(deletedPaths.concat(Array.from(pathsToDelete)));
|
|
94
|
+
}
|
|
95
|
+
|
|
75
96
|
// Build success message
|
|
76
97
|
const successMessage = `deleteDocument executed successfully.
|
|
77
98
|
Successfully deleted document '${documentToDelete.title}' with path '${path}'${recursive && deletedCount > 0 ? ` along with ${deletedCount} child document(s)` : ""}.
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
|
|
3
|
+
import { recordUpdate } from "../../../utils/history-utils.mjs";
|
|
3
4
|
/**
|
|
4
5
|
* Print summary of added documents and documents with new links
|
|
5
6
|
*/
|
|
6
7
|
export default async function printAddDocumentSummary({
|
|
7
8
|
newDocuments = [],
|
|
8
9
|
documentsWithNewLinks = [],
|
|
10
|
+
allFeedback = [],
|
|
9
11
|
}) {
|
|
10
|
-
let message = `\n
|
|
11
|
-
message += `${chalk.bold.cyan("š Summary")}\n`;
|
|
12
|
-
|
|
12
|
+
let message = `\n---\n`;
|
|
13
|
+
message += `${chalk.bold.cyan("š Summary")}\n\n`;
|
|
14
|
+
|
|
15
|
+
// Record the update
|
|
16
|
+
if (allFeedback.length > 0) {
|
|
17
|
+
recordUpdate({
|
|
18
|
+
operation: "structure_update",
|
|
19
|
+
feedback: allFeedback.join("\n"),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
13
22
|
|
|
14
23
|
// Display added documents
|
|
15
24
|
if (newDocuments && newDocuments.length > 0) {
|
|
@@ -47,8 +56,6 @@ export default async function printAddDocumentSummary({
|
|
|
47
56
|
message += `${chalk.gray(" No documents needed to be updated.\n\n")}`;
|
|
48
57
|
}
|
|
49
58
|
|
|
50
|
-
message += `${"=".repeat(80)}\n\n`;
|
|
51
|
-
|
|
52
59
|
return { message };
|
|
53
60
|
}
|
|
54
61
|
|
|
@@ -1,24 +1,61 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import pLimit from "p-limit";
|
|
4
|
+
import { generateFileName, pathToFlatName } from "../../../utils/docs-finder-utils.mjs";
|
|
5
|
+
import { pathExists } from "../../../utils/file-utils.mjs";
|
|
6
|
+
|
|
1
7
|
/**
|
|
2
8
|
* Review documentsWithNewLinks and let user select which documents should be updated
|
|
3
9
|
*/
|
|
4
10
|
export default async function reviewDocumentsWithNewLinks(
|
|
5
|
-
{ documentsWithNewLinks = [], documentExecutionStructure = [] },
|
|
11
|
+
{ documentsWithNewLinks = [], documentExecutionStructure = [], locale = "en", docsDir },
|
|
6
12
|
options,
|
|
7
13
|
) {
|
|
8
14
|
// If no documents to review, return empty array
|
|
9
15
|
if (!documentsWithNewLinks || documentsWithNewLinks.length === 0) {
|
|
10
|
-
return { documentsWithNewLinks: [] };
|
|
16
|
+
return { documentsWithNewLinks: [], documentsToUpdate: [] };
|
|
11
17
|
}
|
|
12
18
|
|
|
19
|
+
// Build choices with file existence check
|
|
20
|
+
const limit = pLimit(50);
|
|
21
|
+
const choices = await Promise.all(
|
|
22
|
+
documentsWithNewLinks.map((document, index) =>
|
|
23
|
+
limit(async () => {
|
|
24
|
+
// Find corresponding document in documentStructure to get title
|
|
25
|
+
const structureDoc = documentExecutionStructure.find((item) => item.path === document.path);
|
|
26
|
+
const title = structureDoc?.title || document.path;
|
|
27
|
+
|
|
28
|
+
// Generate filename from document path
|
|
29
|
+
const flatName = pathToFlatName(document.path);
|
|
30
|
+
const filename = generateFileName(flatName, locale);
|
|
31
|
+
|
|
32
|
+
// Check file existence if docsDir is provided
|
|
33
|
+
let fileExists = true;
|
|
34
|
+
let missingFileText = "";
|
|
35
|
+
if (docsDir) {
|
|
36
|
+
const filePath = join(docsDir, filename);
|
|
37
|
+
fileExists = await pathExists(filePath);
|
|
38
|
+
if (!fileExists) {
|
|
39
|
+
missingFileText = chalk.red(" - file not found");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
name: `${title} (${filename})${missingFileText}`,
|
|
45
|
+
value: index,
|
|
46
|
+
checked: fileExists, // Only check if file exists
|
|
47
|
+
disabled: !fileExists, // Disable if file doesn't exist
|
|
48
|
+
description: `New Links: ${document.newLinks.join(", ")}`,
|
|
49
|
+
};
|
|
50
|
+
}),
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
|
|
13
54
|
// Let user select which documents to update (default: all selected)
|
|
14
55
|
const selectedDocs = await options.prompts.checkbox({
|
|
15
56
|
message:
|
|
16
57
|
"Select documents that need new links added (all selected by default, press Enter to confirm, or unselect all to skip):",
|
|
17
|
-
choices
|
|
18
|
-
name: `${document.path} ā ${document.newLinks.join(", ")}`,
|
|
19
|
-
value: index,
|
|
20
|
-
checked: true, // Default to all selected
|
|
21
|
-
})),
|
|
58
|
+
choices,
|
|
22
59
|
});
|
|
23
60
|
|
|
24
61
|
// Filter documentsWithNewLinks based on user selection
|
|
@@ -42,7 +79,7 @@ export default async function reviewDocumentsWithNewLinks(
|
|
|
42
79
|
};
|
|
43
80
|
}
|
|
44
81
|
|
|
45
|
-
// Prepare documents: add necessary fields for update (
|
|
82
|
+
// Prepare documents: add necessary fields for update (e.g. feedback)
|
|
46
83
|
const preparedDocs = [];
|
|
47
84
|
|
|
48
85
|
for (const doc of filteredDocs) {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
1
2
|
import {
|
|
2
3
|
buildAllowedLinksFromStructure,
|
|
3
4
|
generateFileName,
|
|
4
5
|
pathToFlatName,
|
|
5
6
|
readFileContent,
|
|
6
7
|
} from "../../../utils/docs-finder-utils.mjs";
|
|
8
|
+
import { pathExists } from "../../../utils/file-utils.mjs";
|
|
7
9
|
import { checkMarkdown, getLinkFromError } from "../../../utils/markdown-checker.mjs";
|
|
8
10
|
|
|
9
11
|
export default async function findDocumentsWithInvalidLinks({
|
|
@@ -35,6 +37,15 @@ export default async function findDocumentsWithInvalidLinks({
|
|
|
35
37
|
const flatName = pathToFlatName(doc.path);
|
|
36
38
|
const fileName = generateFileName(flatName, locale);
|
|
37
39
|
|
|
40
|
+
// Check if file exists before reading
|
|
41
|
+
const filePath = join(docsDir, fileName);
|
|
42
|
+
const fileExists = await pathExists(filePath);
|
|
43
|
+
|
|
44
|
+
if (!fileExists) {
|
|
45
|
+
// Skip if file doesn't exist
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
38
49
|
// Read document content
|
|
39
50
|
const content = await readFileContent(docsDir, fileName);
|
|
40
51
|
|
|
@@ -7,9 +7,8 @@ export default async function printRemoveDocumentSummary({
|
|
|
7
7
|
deletedDocuments = [],
|
|
8
8
|
documentsWithInvalidLinks = [],
|
|
9
9
|
}) {
|
|
10
|
-
let message = `\n
|
|
11
|
-
message += `${chalk.bold.cyan("š Summary")}\n`;
|
|
12
|
-
message += `${"=".repeat(80)}\n\n`;
|
|
10
|
+
let message = `\n---\n`;
|
|
11
|
+
message += `${chalk.bold.cyan("š Summary")}\n\n`;
|
|
13
12
|
|
|
14
13
|
// Display removed documents
|
|
15
14
|
if (deletedDocuments && deletedDocuments.length > 0) {
|
|
@@ -47,8 +46,6 @@ export default async function printRemoveDocumentSummary({
|
|
|
47
46
|
message += `${chalk.gray(" No documents needed to be fixed.\n\n")}`;
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
message += `${"=".repeat(80)}\n\n`;
|
|
51
|
-
|
|
52
49
|
return { message };
|
|
53
50
|
}
|
|
54
51
|
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import chooseDocs from "../../utils/choose-docs.mjs";
|
|
2
1
|
import deleteDocument from "../document-structure-tools/delete-document.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import addTranslatesToStructure from "../../utils/add-translates-to-structure.mjs";
|
|
2
|
+
import { buildDocumentTree, buildChoicesFromTree } from "../../../utils/docs-finder-utils.mjs";
|
|
5
3
|
|
|
6
4
|
export default async function removeDocumentsFromStructure(input = {}, options = {}) {
|
|
7
|
-
const {
|
|
5
|
+
const { originalDocumentStructure, locale = "en", docsDir } = input;
|
|
8
6
|
|
|
9
7
|
if (!Array.isArray(originalDocumentStructure) || originalDocumentStructure.length === 0) {
|
|
10
8
|
console.warn(
|
|
@@ -13,43 +11,43 @@ export default async function removeDocumentsFromStructure(input = {}, options =
|
|
|
13
11
|
process.exit(0);
|
|
14
12
|
}
|
|
15
13
|
|
|
16
|
-
const { documentExecutionStructure } = addTranslatesToStructure({
|
|
17
|
-
originalDocumentStructure,
|
|
18
|
-
translateLanguages,
|
|
19
|
-
});
|
|
20
|
-
|
|
21
14
|
// Initialize currentStructure in userContext
|
|
22
15
|
options.context.userContext.currentStructure = [...originalDocumentStructure];
|
|
23
16
|
|
|
24
|
-
//
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
17
|
+
// Build tree structure
|
|
18
|
+
const { rootNodes } = buildDocumentTree(originalDocumentStructure);
|
|
19
|
+
|
|
20
|
+
// Build choices with tree structure visualization
|
|
21
|
+
const choices = await buildChoicesFromTree(rootNodes, "", 0, { locale, docsDir });
|
|
22
|
+
|
|
23
|
+
// Let user select documents to delete
|
|
24
|
+
let selectedPaths = [];
|
|
25
|
+
try {
|
|
26
|
+
selectedPaths = await options.prompts.checkbox({
|
|
27
|
+
message: "Select documents to remove (Press Enter with no selection to finish):",
|
|
28
|
+
choices,
|
|
29
|
+
});
|
|
30
|
+
} catch {
|
|
31
|
+
// User cancelled or no selection made
|
|
32
|
+
console.log("No documents were removed.");
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
// If no documents selected, exit
|
|
37
|
+
if (!selectedPaths || selectedPaths.length === 0) {
|
|
38
|
+
console.log("No documents were removed.");
|
|
41
39
|
process.exit(0);
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
// Delete each selected document
|
|
42
|
+
// Delete each selected document with cascade deletion
|
|
45
43
|
const deletedDocuments = [];
|
|
46
44
|
const errors = [];
|
|
47
45
|
|
|
48
|
-
for (const
|
|
46
|
+
for (const path of selectedPaths) {
|
|
49
47
|
try {
|
|
50
48
|
const deleteResult = await deleteDocument(
|
|
51
49
|
{
|
|
52
|
-
path
|
|
50
|
+
path,
|
|
53
51
|
recursive: true,
|
|
54
52
|
},
|
|
55
53
|
options,
|
|
@@ -57,21 +55,21 @@ export default async function removeDocumentsFromStructure(input = {}, options =
|
|
|
57
55
|
|
|
58
56
|
if (deleteResult.error) {
|
|
59
57
|
errors.push({
|
|
60
|
-
path
|
|
58
|
+
path,
|
|
61
59
|
error: deleteResult.error.message,
|
|
62
60
|
});
|
|
63
61
|
} else {
|
|
64
|
-
// deletedDocuments is now always an array
|
|
65
62
|
deletedDocuments.push(...deleteResult.deletedDocuments);
|
|
66
63
|
}
|
|
67
64
|
} catch (error) {
|
|
68
65
|
errors.push({
|
|
69
|
-
path
|
|
66
|
+
path,
|
|
70
67
|
error: error.message,
|
|
71
68
|
});
|
|
72
69
|
}
|
|
73
70
|
}
|
|
74
71
|
|
|
72
|
+
// Check if there are errors
|
|
75
73
|
if (errors.length > 0) {
|
|
76
74
|
console.warn(
|
|
77
75
|
`šļø Remove Documents\n ⢠Failed to remove documents:\n${errors
|
|
@@ -81,7 +79,12 @@ export default async function removeDocumentsFromStructure(input = {}, options =
|
|
|
81
79
|
process.exit(0);
|
|
82
80
|
}
|
|
83
81
|
|
|
84
|
-
|
|
82
|
+
if (deletedDocuments.length === 0) {
|
|
83
|
+
console.log("No documents were removed.");
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Get final updated document structure
|
|
85
88
|
const updatedStructure = options.context.userContext.currentStructure;
|
|
86
89
|
|
|
87
90
|
return {
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
buildAllowedLinksFromStructure,
|
|
3
|
+
generateFileName,
|
|
4
|
+
pathToFlatName,
|
|
5
|
+
} from "../../../utils/docs-finder-utils.mjs";
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* Generate feedback message for fixing invalid links in a document
|
|
@@ -44,27 +48,26 @@ ${allowedLinksList}
|
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
export default async function reviewDocumentsWithInvalidLinks(input = {}, options = {}) {
|
|
47
|
-
const { documentsWithInvalidLinks = [], documentExecutionStructure = [] } = input;
|
|
51
|
+
const { documentsWithInvalidLinks = [], documentExecutionStructure = [], locale = "en" } = input;
|
|
48
52
|
|
|
49
53
|
// If no documents with invalid links, return empty array
|
|
50
54
|
if (!Array.isArray(documentsWithInvalidLinks) || documentsWithInvalidLinks.length === 0) {
|
|
51
55
|
return {
|
|
52
56
|
documentsWithInvalidLinks: [],
|
|
57
|
+
documentsToUpdate: [],
|
|
53
58
|
};
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
// Create choices for user selection, default all checked
|
|
57
62
|
const choices = documentsWithInvalidLinks.map((doc) => {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
? ` (${doc.invalidLinks.length} invalid link${doc.invalidLinks.length > 1 ? "s" : ""})`
|
|
61
|
-
: "";
|
|
63
|
+
const flatName = pathToFlatName(doc.path);
|
|
64
|
+
const filename = generateFileName(flatName, locale);
|
|
62
65
|
|
|
63
66
|
return {
|
|
64
|
-
name: `${doc.title
|
|
67
|
+
name: `${doc.title} (${filename})`,
|
|
65
68
|
value: doc.path,
|
|
66
69
|
checked: true, // Default all selected
|
|
67
|
-
description: `Invalid Links: ${doc.invalidLinks
|
|
70
|
+
description: `Invalid Links(${doc.invalidLinks?.length || 0}): ${doc.invalidLinks?.join(", ")}`,
|
|
68
71
|
};
|
|
69
72
|
});
|
|
70
73
|
|
package/agents/init/index.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
SUPPORTED_LANGUAGES,
|
|
15
15
|
TARGET_AUDIENCES,
|
|
16
16
|
} from "../../utils/constants/index.mjs";
|
|
17
|
+
import { isRemoteFile } from "../../utils/file-utils.mjs";
|
|
17
18
|
import loadConfig from "../../utils/load-config.mjs";
|
|
18
19
|
import {
|
|
19
20
|
detectSystemLanguage,
|
|
@@ -22,9 +23,8 @@ import {
|
|
|
22
23
|
isGlobPattern,
|
|
23
24
|
validatePath,
|
|
24
25
|
} from "../../utils/utils.mjs";
|
|
25
|
-
import { isRemoteFile } from "../../utils/file-utils.mjs";
|
|
26
|
-
import { validateDocDir } from "./validate.mjs";
|
|
27
26
|
import mapReasoningEffortLevel from "../utils/map-reasoning-effort-level.mjs";
|
|
27
|
+
import { validateDocDir } from "./validate.mjs";
|
|
28
28
|
|
|
29
29
|
const _PRESS_ENTER_TO_FINISH = "Press Enter to finish";
|
|
30
30
|
|
|
@@ -362,8 +362,7 @@ async function _init(
|
|
|
362
362
|
continue;
|
|
363
363
|
}
|
|
364
364
|
sourcePaths.push(trimmedPath);
|
|
365
|
-
}
|
|
366
|
-
if (isGlobPatternResult) {
|
|
365
|
+
} else if (isGlobPatternResult) {
|
|
367
366
|
// For glob patterns, just add them without validation
|
|
368
367
|
if (sourcePaths.includes(trimmedPath)) {
|
|
369
368
|
console.log(`ā ļø Pattern already exists: ${trimmedPath}`);
|
|
@@ -1,27 +1,42 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
1
|
import { statSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import imageSize from "image-size";
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_EXCLUDE_PATTERNS,
|
|
7
|
+
DEFAULT_INCLUDE_PATTERNS,
|
|
8
|
+
INTELLIGENT_SUGGESTION_TOKEN_THRESHOLD,
|
|
9
|
+
} from "../../utils/constants/index.mjs";
|
|
10
|
+
import { loadDocumentStructure } from "../../utils/docs-finder-utils.mjs";
|
|
5
11
|
import {
|
|
6
12
|
buildSourcesContent,
|
|
7
|
-
|
|
8
|
-
readFileContents,
|
|
13
|
+
calculateTokens,
|
|
9
14
|
getMimeType,
|
|
10
15
|
isRemoteFile,
|
|
11
|
-
|
|
16
|
+
loadFilesFromPaths,
|
|
17
|
+
readFileContents,
|
|
12
18
|
} from "../../utils/file-utils.mjs";
|
|
19
|
+
import { isOpenAPISpecFile } from "../../utils/openapi/index.mjs";
|
|
13
20
|
import {
|
|
14
21
|
getCurrentGitHead,
|
|
15
22
|
getModifiedFilesBetweenCommits,
|
|
16
23
|
toRelativePath,
|
|
17
24
|
} from "../../utils/utils.mjs";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
|
|
26
|
+
const imageExts = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg", ".heic", ".heif"];
|
|
27
|
+
const videoExts = [
|
|
28
|
+
".mp4",
|
|
29
|
+
".mpeg",
|
|
30
|
+
".mpg",
|
|
31
|
+
".mov",
|
|
32
|
+
".avi",
|
|
33
|
+
".flv",
|
|
34
|
+
".mkv",
|
|
35
|
+
".webm",
|
|
36
|
+
".wmv",
|
|
37
|
+
".m4v",
|
|
38
|
+
".3gpp",
|
|
39
|
+
];
|
|
25
40
|
|
|
26
41
|
export default async function loadSources(
|
|
27
42
|
{
|
|
@@ -77,7 +92,13 @@ export default async function loadSources(
|
|
|
77
92
|
.filter(Boolean);
|
|
78
93
|
const allFiles = await loadFilesFromPaths(pickSourcesPath, {
|
|
79
94
|
includePatterns,
|
|
80
|
-
excludePatterns: [
|
|
95
|
+
excludePatterns: [
|
|
96
|
+
...new Set([
|
|
97
|
+
...(excludePatterns || []),
|
|
98
|
+
...customExcludePatterns,
|
|
99
|
+
...videoExts.map((x) => `**/*${x}`),
|
|
100
|
+
]),
|
|
101
|
+
],
|
|
81
102
|
useDefaultPatterns,
|
|
82
103
|
defaultIncludePatterns: DEFAULT_INCLUDE_PATTERNS,
|
|
83
104
|
defaultExcludePatterns: DEFAULT_EXCLUDE_PATTERNS,
|
|
@@ -90,26 +111,9 @@ export default async function loadSources(
|
|
|
90
111
|
|
|
91
112
|
// Define media file extensions
|
|
92
113
|
const mediaExtensions = [
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
".gif",
|
|
97
|
-
".bmp",
|
|
98
|
-
".webp",
|
|
99
|
-
".svg",
|
|
100
|
-
".heic",
|
|
101
|
-
".heif",
|
|
102
|
-
".mp4",
|
|
103
|
-
".mpeg",
|
|
104
|
-
".mpg",
|
|
105
|
-
".mov",
|
|
106
|
-
".avi",
|
|
107
|
-
".flv",
|
|
108
|
-
".mkv",
|
|
109
|
-
".webm",
|
|
110
|
-
".wmv",
|
|
111
|
-
".m4v",
|
|
112
|
-
".3gpp",
|
|
114
|
+
...imageExts,
|
|
115
|
+
// ignore video temporary
|
|
116
|
+
// ...videoExts
|
|
113
117
|
];
|
|
114
118
|
|
|
115
119
|
// Separate source files from media files
|
|
@@ -119,20 +123,6 @@ export default async function loadSources(
|
|
|
119
123
|
// Helper function to determine file type from extension
|
|
120
124
|
const getFileType = (filePath) => {
|
|
121
125
|
const ext = path.extname(filePath).toLowerCase();
|
|
122
|
-
const imageExts = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg", ".heic", ".heif"];
|
|
123
|
-
const videoExts = [
|
|
124
|
-
".mp4",
|
|
125
|
-
".mpeg",
|
|
126
|
-
".mpg",
|
|
127
|
-
".mov",
|
|
128
|
-
".avi",
|
|
129
|
-
".flv",
|
|
130
|
-
".mkv",
|
|
131
|
-
".webm",
|
|
132
|
-
".wmv",
|
|
133
|
-
".m4v",
|
|
134
|
-
".3gpp",
|
|
135
|
-
];
|
|
136
126
|
|
|
137
127
|
if (imageExts.includes(ext)) return "image";
|
|
138
128
|
if (videoExts.includes(ext)) return "video";
|
package/aigne.yaml
CHANGED
|
@@ -132,7 +132,7 @@ cli:
|
|
|
132
132
|
alias: ["add"]
|
|
133
133
|
url: ./agents/create/user-add-document/index.yaml
|
|
134
134
|
- name: remove-document
|
|
135
|
-
alias: ["rm"]
|
|
135
|
+
alias: ["remove", "rm"]
|
|
136
136
|
url: ./agents/create/user-remove-document/index.yaml
|
|
137
137
|
- ./agents/clear/index.yaml
|
|
138
138
|
mcp_server:
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@ XCards is multiple `<x-card>` container, suitable for displaying multiple links
|
|
|
5
5
|
|
|
6
6
|
### Attributes
|
|
7
7
|
|
|
8
|
-
- `data-columns` (
|
|
8
|
+
- `data-columns` (required): must be an integer ā„ 2; no upper bound.
|
|
9
9
|
|
|
10
10
|
### Children
|
|
11
11
|
|
|
@@ -20,20 +20,21 @@ XCards is multiple `<x-card>` container, suitable for displaying multiple links
|
|
|
20
20
|
|
|
21
21
|
### Good Examples
|
|
22
22
|
|
|
23
|
-
- Example 1:
|
|
23
|
+
- Example 1: Two-column cards with images
|
|
24
24
|
```md
|
|
25
|
-
<x-cards data-columns="
|
|
26
|
-
<x-card data-title="
|
|
27
|
-
<x-card data-title="
|
|
28
|
-
<x-card data-title="Feature 3" data-icon="material-symbols:rocket-outline">Description of Feature 3.</x-card>
|
|
25
|
+
<x-cards data-columns="2">
|
|
26
|
+
<x-card data-title="Card A" data-image="https://picsum.photos/id/10/300/300">Content A</x-card>
|
|
27
|
+
<x-card data-title="Card B" data-image="https://picsum.photos/id/11/300/300">Content B</x-card>
|
|
29
28
|
</x-cards>
|
|
30
29
|
```
|
|
31
30
|
|
|
32
|
-
- Example 2:
|
|
31
|
+
- Example 2: Four-column cards with icons
|
|
33
32
|
```md
|
|
34
|
-
<x-cards data-columns="
|
|
35
|
-
<x-card data-title="
|
|
36
|
-
<x-card data-title="
|
|
33
|
+
<x-cards data-columns="4">
|
|
34
|
+
<x-card data-title="Feature 1" data-icon="lucide:rocket">Description of Feature 1.</x-card>
|
|
35
|
+
<x-card data-title="Feature 2" data-icon="lucide:bolt">Description of Feature 2.</x-card>
|
|
36
|
+
<x-card data-title="Feature 3" data-icon="material-symbols:rocket-outline">Description of Feature 3.</x-card>
|
|
37
|
+
<x-card data-title="Feature 4" data-icon="lucide:star">Description of Feature 4.</x-card>
|
|
37
38
|
</x-cards>
|
|
38
39
|
```
|
|
39
40
|
|
|
@@ -72,4 +73,11 @@ XCards is multiple `<x-card>` container, suitable for displaying multiple links
|
|
|
72
73
|
- [Using the Blog](./blog.md)
|
|
73
74
|
- [Using Chat](./chat.md)
|
|
74
75
|
```
|
|
76
|
+
|
|
77
|
+
- Example 4: Missing `data-columns` attribute (required)
|
|
78
|
+
<x-cards>
|
|
79
|
+
<x-card data-title="Feature 1" data-icon="lucide:rocket">Description of Feature 1.</x-card>
|
|
80
|
+
<x-card data-title="Feature 2" data-icon="lucide:bolt">Description of Feature 2.</x-card>
|
|
81
|
+
</x-cards>
|
|
82
|
+
|
|
75
83
|
</x-card-usage-rules>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { access, readdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import pLimit from "p-limit";
|
|
3
5
|
import { pathExists } from "./file-utils.mjs";
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -393,6 +395,83 @@ export function buildDocumentTree(documentStructure) {
|
|
|
393
395
|
return { rootNodes, nodeMap };
|
|
394
396
|
}
|
|
395
397
|
|
|
398
|
+
/**
|
|
399
|
+
* Build checkbox choices from tree structure with visual hierarchy
|
|
400
|
+
* @param {Array} nodes - Array of tree nodes
|
|
401
|
+
* @param {string} prefix - Current prefix for indentation
|
|
402
|
+
* @param {number} depth - Current depth level (0 for root)
|
|
403
|
+
* @param {Object} context - Context object containing locale, docsDir, etc.
|
|
404
|
+
* @param {string} context.locale - Main language locale (e.g., 'en', 'zh', 'fr')
|
|
405
|
+
* @param {string} [context.docsDir] - Docs directory path for file existence check
|
|
406
|
+
* @returns {Promise<Array>} Array of choice objects
|
|
407
|
+
*/
|
|
408
|
+
export async function buildChoicesFromTree(nodes, prefix = "", depth = 0, context = {}) {
|
|
409
|
+
const { locale = "en", docsDir } = context;
|
|
410
|
+
const choices = [];
|
|
411
|
+
|
|
412
|
+
// Limit concurrent file checks to 50 per level to avoid overwhelming the file system
|
|
413
|
+
const limit = pLimit(50);
|
|
414
|
+
|
|
415
|
+
// Process nodes with controlled concurrency while maintaining order
|
|
416
|
+
const nodePromises = nodes.map((node, i) =>
|
|
417
|
+
limit(async () => {
|
|
418
|
+
const isLastSibling = i === nodes.length - 1;
|
|
419
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
420
|
+
|
|
421
|
+
// Build the tree prefix - top level nodes don't have āā or āā
|
|
422
|
+
const treePrefix = depth === 0 ? "" : prefix + (isLastSibling ? "āā " : "āā ");
|
|
423
|
+
const flatName = pathToFlatName(node.path);
|
|
424
|
+
const filename = generateFileName(flatName, locale);
|
|
425
|
+
|
|
426
|
+
// Check file existence if docsDir is provided
|
|
427
|
+
let fileExists = true;
|
|
428
|
+
let missingFileText = "";
|
|
429
|
+
if (docsDir) {
|
|
430
|
+
const filePath = join(docsDir, filename);
|
|
431
|
+
fileExists = await pathExists(filePath);
|
|
432
|
+
if (!fileExists) {
|
|
433
|
+
missingFileText = chalk.red(" - file not found");
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// warningText only shows when file exists, missingFileText has higher priority
|
|
438
|
+
const warningText =
|
|
439
|
+
fileExists && hasChildren ? chalk.yellow(" - will cascade delete all child documents") : "";
|
|
440
|
+
|
|
441
|
+
const displayName = `${treePrefix}${node.title} (${filename})${warningText}${missingFileText}`;
|
|
442
|
+
|
|
443
|
+
const choice = {
|
|
444
|
+
name: displayName,
|
|
445
|
+
value: node.path,
|
|
446
|
+
short: node.title,
|
|
447
|
+
disabled: !fileExists,
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
// Recursively process children
|
|
451
|
+
let childChoices = [];
|
|
452
|
+
if (hasChildren) {
|
|
453
|
+
const childPrefix = depth === 0 ? "" : prefix + (isLastSibling ? " " : "ā ");
|
|
454
|
+
childChoices = await buildChoicesFromTree(node.children, childPrefix, depth + 1, context);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return { choice, childChoices };
|
|
458
|
+
}),
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
// Wait for all nodes at this level to complete, maintaining order
|
|
462
|
+
const results = await Promise.all(nodePromises);
|
|
463
|
+
|
|
464
|
+
// Build choices array in order
|
|
465
|
+
for (const { choice, childChoices } of results) {
|
|
466
|
+
choices.push(choice);
|
|
467
|
+
if (childChoices.length > 0) {
|
|
468
|
+
choices.push(...childChoices);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return choices;
|
|
473
|
+
}
|
|
474
|
+
|
|
396
475
|
/**
|
|
397
476
|
* Format document structure for printing
|
|
398
477
|
* @param {Array} structure - Document structure array
|
package/utils/file-utils.mjs
CHANGED
|
@@ -900,17 +900,19 @@ export function isDirExcluded(dir, excludePatterns) {
|
|
|
900
900
|
|
|
901
901
|
/**
|
|
902
902
|
* Return source paths that would be excluded by exclude patterns (files are skipped, directories use minimatch, glob patterns use path prefix heuristic)
|
|
903
|
+
* @returns {{excluded: string[], notFound: string[]}} Object with excluded and notFound arrays
|
|
903
904
|
*/
|
|
904
905
|
export async function findInvalidSourcePaths(sourcePaths, excludePatterns) {
|
|
905
906
|
if (!Array.isArray(sourcePaths) || sourcePaths.length === 0) {
|
|
906
|
-
return [];
|
|
907
|
+
return { excluded: [], notFound: [] };
|
|
907
908
|
}
|
|
908
909
|
|
|
909
910
|
if (!Array.isArray(excludePatterns) || excludePatterns.length === 0) {
|
|
910
|
-
return [];
|
|
911
|
+
return { excluded: [], notFound: [] };
|
|
911
912
|
}
|
|
912
913
|
|
|
913
|
-
const
|
|
914
|
+
const excluded = [];
|
|
915
|
+
const notFound = [];
|
|
914
916
|
|
|
915
917
|
for (const sourcePath of sourcePaths) {
|
|
916
918
|
if (typeof sourcePath !== "string" || !sourcePath) {
|
|
@@ -931,7 +933,7 @@ export async function findInvalidSourcePaths(sourcePaths, excludePatterns) {
|
|
|
931
933
|
if (isGlobPattern(sourcePath)) {
|
|
932
934
|
const representativePath = getPathPrefix(sourcePath);
|
|
933
935
|
if (isDirExcluded(representativePath, excludePatterns)) {
|
|
934
|
-
|
|
936
|
+
excluded.push(sourcePath);
|
|
935
937
|
}
|
|
936
938
|
continue;
|
|
937
939
|
}
|
|
@@ -945,14 +947,14 @@ export async function findInvalidSourcePaths(sourcePaths, excludePatterns) {
|
|
|
945
947
|
// Check dir with minimatch
|
|
946
948
|
if (stats.isDirectory()) {
|
|
947
949
|
if (isDirExcluded(sourcePath, excludePatterns)) {
|
|
948
|
-
|
|
950
|
+
excluded.push(sourcePath);
|
|
949
951
|
}
|
|
950
952
|
}
|
|
951
953
|
} catch {
|
|
952
954
|
// Path doesn't exist
|
|
953
|
-
|
|
955
|
+
notFound.push(sourcePath);
|
|
954
956
|
}
|
|
955
957
|
}
|
|
956
958
|
|
|
957
|
-
return
|
|
959
|
+
return { excluded, notFound };
|
|
958
960
|
}
|
package/utils/load-config.mjs
CHANGED
|
@@ -41,11 +41,28 @@ export default async function loadConfig({ config, appUrl }) {
|
|
|
41
41
|
...(processedConfig.excludePatterns || parsedConfig.excludePatterns || []),
|
|
42
42
|
];
|
|
43
43
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
const { excluded, notFound } = await findInvalidSourcePaths(sourcesPath, excludePatterns);
|
|
45
|
+
|
|
46
|
+
if (excluded.length > 0 || notFound.length > 0) {
|
|
47
|
+
const warnings = [];
|
|
48
|
+
|
|
49
|
+
if (excluded.length > 0) {
|
|
50
|
+
warnings.push(
|
|
51
|
+
`ā ļø These paths were excluded (ignored by config):\n${excluded.map((p) => ` - ${chalk.yellow(p)}`).join("\n")}`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (notFound.length > 0) {
|
|
56
|
+
warnings.push(
|
|
57
|
+
`š« These paths were skipped because they do not exist:\n${notFound.map((p) => ` - ${chalk.red(p)}`).join("\n")}`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
warnings.push(
|
|
62
|
+
`š” Tip: You can remove these paths in ${chalk.cyan(toDisplayPath(configPath))}`,
|
|
48
63
|
);
|
|
64
|
+
|
|
65
|
+
console.warn(`${warnings.join("\n\n")}\n`);
|
|
49
66
|
}
|
|
50
67
|
}
|
|
51
68
|
|