@garyr/pt-cli 0.38.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/addCommand.js +98 -8
- package/dist/commands/configCommand.js +11 -4
- package/dist/commands/learnCommand.js +8 -4
- package/dist/commands/updateCommand.js +784 -0
- package/dist/config.js +12 -2
- package/dist/index.js +9 -4
- package/doc/configuration.md +18 -8
- package/doc/usage.md +9 -0
- package/package.json +1 -1
- package/skills/agency-pt-operator/SKILL.md +6 -2
- package/src/commands/addCommand.ts +109 -8
- package/src/commands/configCommand.ts +11 -4
- package/src/commands/learnCommand.ts +12 -7
- package/src/commands/updateCommand.ts +835 -0
- package/src/config.ts +14 -2
- package/src/index.ts +9 -4
|
@@ -2,24 +2,114 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { loadConfig, saveConfig } from '../config.js';
|
|
5
|
+
/**
|
|
6
|
+
* Validate JSON file exists and contains valid JSON
|
|
7
|
+
*/
|
|
8
|
+
function validateJsonFile(filePath) {
|
|
9
|
+
try {
|
|
10
|
+
// Check if file exists
|
|
11
|
+
if (!fs.existsSync(filePath)) {
|
|
12
|
+
return {
|
|
13
|
+
valid: false,
|
|
14
|
+
error: `File not found: ${filePath}`
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
// Check file size (reasonable limit to prevent reading huge files)
|
|
18
|
+
const stats = fs.statSync(filePath);
|
|
19
|
+
if (stats.size > 10 * 1024 * 1024) { // 10MB limit
|
|
20
|
+
return {
|
|
21
|
+
valid: false,
|
|
22
|
+
error: `File too large (${stats.size} bytes). Maximum size is 10MB.`
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
// Read and parse JSON
|
|
26
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
27
|
+
if (!content.trim()) {
|
|
28
|
+
return {
|
|
29
|
+
valid: false,
|
|
30
|
+
error: 'File is empty'
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const data = JSON.parse(content);
|
|
34
|
+
return { valid: true, data };
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
const error = e;
|
|
38
|
+
return {
|
|
39
|
+
valid: false,
|
|
40
|
+
error: `JSON parse error: ${error.message}`
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Validate template structure
|
|
46
|
+
*/
|
|
47
|
+
function validateTemplateStructure(data) {
|
|
48
|
+
if (!data || typeof data !== 'object') {
|
|
49
|
+
return {
|
|
50
|
+
valid: false,
|
|
51
|
+
error: 'Template data must be a JSON object'
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// Basic structure validation
|
|
55
|
+
if (data.description && typeof data.description !== 'string') {
|
|
56
|
+
return {
|
|
57
|
+
valid: false,
|
|
58
|
+
error: 'Template description must be a string'
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (data.variables && Array.isArray(data.variables)) {
|
|
62
|
+
for (let i = 0; i < data.variables.length; i++) {
|
|
63
|
+
const v = data.variables[i];
|
|
64
|
+
if (!v.name || typeof v.name !== 'string') {
|
|
65
|
+
return {
|
|
66
|
+
valid: false,
|
|
67
|
+
error: `Variable at index ${i} must have a string 'name' field`
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { valid: true };
|
|
73
|
+
}
|
|
5
74
|
export function addCommand(name, jsonStr, options = {}) {
|
|
6
75
|
const config = loadConfig();
|
|
76
|
+
// Determine if we're reading from file or string
|
|
77
|
+
const isFile = !!options.file;
|
|
78
|
+
let data;
|
|
7
79
|
try {
|
|
8
|
-
|
|
9
|
-
|
|
80
|
+
if (isFile) {
|
|
81
|
+
// Validate file first
|
|
10
82
|
const filePath = path.resolve(options.file);
|
|
11
|
-
|
|
83
|
+
const validation = validateJsonFile(filePath);
|
|
84
|
+
if (!validation.valid) {
|
|
85
|
+
console.error(chalk.red(`Error: ${validation.error}`));
|
|
86
|
+
console.error(chalk.gray(`File: ${filePath}`));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
data = validation.data;
|
|
12
90
|
}
|
|
13
91
|
else if (jsonStr) {
|
|
14
|
-
|
|
92
|
+
// Parse JSON string directly
|
|
93
|
+
try {
|
|
94
|
+
data = JSON.parse(jsonStr);
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
const error = e;
|
|
98
|
+
console.error(chalk.red(`Error: Invalid JSON string - ${error.message}`));
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
15
101
|
}
|
|
16
102
|
else {
|
|
17
103
|
console.error('Error: Either a JSON string or --file <path> must be provided.');
|
|
18
104
|
process.exit(1);
|
|
19
105
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
106
|
+
// Validate template structure
|
|
107
|
+
const structureValidation = validateTemplateStructure(data);
|
|
108
|
+
if (!structureValidation.valid) {
|
|
109
|
+
console.error(chalk.red(`Error: Invalid template structure - ${structureValidation.error}`));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
// Check for full config object
|
|
23
113
|
if (data && data.templates && typeof data.templates === 'object') {
|
|
24
114
|
console.error(chalk.red('Error: The provided JSON appears to be a full configuration file, not a single template.'));
|
|
25
115
|
console.error(chalk.gray('If you want to import a specific template from it, extract that template object first.'));
|
|
@@ -31,7 +121,7 @@ export function addCommand(name, jsonStr, options = {}) {
|
|
|
31
121
|
}
|
|
32
122
|
catch (e) {
|
|
33
123
|
const error = e;
|
|
34
|
-
console.error(chalk.red(`Failed to
|
|
124
|
+
console.error(chalk.red(`Failed to process template: ${error.message}`));
|
|
35
125
|
process.exit(1);
|
|
36
126
|
}
|
|
37
127
|
}
|
|
@@ -9,15 +9,22 @@ export function configCommand(templateName, options = {}) {
|
|
|
9
9
|
name: templateName,
|
|
10
10
|
...config.templates[templateName]
|
|
11
11
|
};
|
|
12
|
-
|
|
12
|
+
// Safely drain stdout before allowing the process to close
|
|
13
|
+
process.stdout.write(JSON.stringify(output, null, 2) + '\n', () => {
|
|
14
|
+
process.exit(0);
|
|
15
|
+
});
|
|
13
16
|
}
|
|
14
17
|
else {
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
process.stderr.write(chalk.red(`Error: Template "${templateName}" not found.\n`), () => {
|
|
19
|
+
process.exit(1);
|
|
20
|
+
});
|
|
17
21
|
}
|
|
18
22
|
}
|
|
19
23
|
else {
|
|
20
|
-
|
|
24
|
+
// Safely drain the entire global config payload
|
|
25
|
+
process.stdout.write(JSON.stringify(config, null, 2) + '\n', () => {
|
|
26
|
+
process.exit(0);
|
|
27
|
+
});
|
|
21
28
|
}
|
|
22
29
|
return;
|
|
23
30
|
}
|
|
@@ -535,7 +535,11 @@ export async function learn(sourcePath, updateTemplate = null, options = {}) {
|
|
|
535
535
|
name: targetName,
|
|
536
536
|
...templateConfig
|
|
537
537
|
};
|
|
538
|
-
|
|
538
|
+
// Force the application to wait until every single byte of this JSON string
|
|
539
|
+
// safely clears the operating system's pipe buffer before letting the process die.
|
|
540
|
+
process.stdout.write(JSON.stringify(output, null, 2) + '\n', () => {
|
|
541
|
+
process.exit(0);
|
|
542
|
+
});
|
|
539
543
|
return;
|
|
540
544
|
}
|
|
541
545
|
config.templates[targetName] = templateConfig;
|
|
@@ -577,10 +581,10 @@ function extractStructure(dirPath, rootPath, ignorePatterns) {
|
|
|
577
581
|
let info = "";
|
|
578
582
|
const gitkeepPath = path.join(fullPath, '.gitkeep.md');
|
|
579
583
|
const infoPath = path.join(fullPath, '.info.md');
|
|
580
|
-
if (fs.existsSync(
|
|
581
|
-
info = fs.readFileSync(gitkeepPath, 'utf-8').trim();
|
|
582
|
-
else if (fs.existsSync(infoPath))
|
|
584
|
+
if (fs.existsSync(infoPath))
|
|
583
585
|
info = fs.readFileSync(infoPath, 'utf-8').trim();
|
|
586
|
+
else if (fs.existsSync(gitkeepPath))
|
|
587
|
+
info = fs.readFileSync(gitkeepPath, 'utf-8').trim();
|
|
584
588
|
nodes.push({ name: entry.name, info: info, children: children });
|
|
585
589
|
}
|
|
586
590
|
}
|