ppcos 0.3.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.
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Input validation utilities
3
+ */
4
+
5
+ // Regex patterns per spec
6
+ const CLIENT_NAME_REGEX = /^[a-z][a-z0-9-]{0,49}$/;
7
+ const GOOGLE_ADS_ID_REGEX = /^\d{3}-\d{3}-\d{4}$/;
8
+
9
+ /**
10
+ * Validate a client name
11
+ * Rules:
12
+ * - Lowercase letters, numbers, hyphens only
13
+ * - Must start with letter
14
+ * - 1-50 characters
15
+ * - No spaces or special characters
16
+ *
17
+ * @param {string} name - Client name to validate
18
+ * @returns {{ valid: boolean, error?: string }} Validation result
19
+ */
20
+ export function validateClientName(name) {
21
+ if (!name || typeof name !== 'string') {
22
+ return { valid: false, error: 'Client name is required' };
23
+ }
24
+
25
+ if (name.length < 1) {
26
+ return { valid: false, error: 'Client name is required' };
27
+ }
28
+
29
+ if (name.length > 50) {
30
+ return { valid: false, error: 'Client name must be 1-50 characters' };
31
+ }
32
+
33
+ if (!/^[a-z]/.test(name)) {
34
+ return { valid: false, error: 'Client name must start with a lowercase letter' };
35
+ }
36
+
37
+ if (!CLIENT_NAME_REGEX.test(name)) {
38
+ return {
39
+ valid: false,
40
+ error: 'Client name can only contain lowercase letters, numbers, and hyphens'
41
+ };
42
+ }
43
+
44
+ return { valid: true };
45
+ }
46
+
47
+ /**
48
+ * Validate a Google Ads customer ID
49
+ * Format: XXX-XXX-XXXX (3 digits, 3 digits, 4 digits)
50
+ *
51
+ * @param {string} id - Customer ID to validate
52
+ * @returns {{ valid: boolean, error?: string }} Validation result
53
+ */
54
+ export function validateGoogleAdsId(id) {
55
+ if (!id) {
56
+ return { valid: true }; // Optional field
57
+ }
58
+
59
+ if (!GOOGLE_ADS_ID_REGEX.test(id)) {
60
+ return {
61
+ valid: false,
62
+ error: 'Google Ads Customer ID must be in format XXX-XXX-XXXX'
63
+ };
64
+ }
65
+
66
+ return { valid: true };
67
+ }
68
+
69
+ /**
70
+ * Validate main-config.json structure
71
+ *
72
+ * @param {object} config - Config object to validate
73
+ * @returns {{ valid: boolean, errors: string[] }} Validation result
74
+ */
75
+ export function validateConfig(config) {
76
+ const errors = [];
77
+
78
+ if (!config || typeof config !== 'object') {
79
+ return { valid: false, errors: ['Config must be an object'] };
80
+ }
81
+
82
+ // Version check
83
+ if (config.version !== '1.0') {
84
+ errors.push('Unsupported config version (expected "1.0")');
85
+ }
86
+
87
+ // Clients array check
88
+ if (!Array.isArray(config.clients)) {
89
+ errors.push('clients must be an array');
90
+ return { valid: false, errors };
91
+ }
92
+
93
+ // Validate each client
94
+ const seenNames = new Set();
95
+
96
+ for (let i = 0; i < config.clients.length; i++) {
97
+ const client = config.clients[i];
98
+ const prefix = `clients[${i}]`;
99
+
100
+ if (!client || typeof client !== 'object') {
101
+ errors.push(`${prefix}: must be an object`);
102
+ continue;
103
+ }
104
+
105
+ // Name validation
106
+ const nameResult = validateClientName(client.name);
107
+ if (!nameResult.valid) {
108
+ errors.push(`${prefix}.name: ${nameResult.error}`);
109
+ } else if (seenNames.has(client.name)) {
110
+ errors.push(`${prefix}.name: duplicate client name "${client.name}"`);
111
+ } else {
112
+ seenNames.add(client.name);
113
+ }
114
+
115
+ // Enabled field
116
+ if (typeof client.enabled !== 'boolean') {
117
+ errors.push(`${prefix}.enabled: must be a boolean`);
118
+ }
119
+
120
+ // Optional Google Ads ID
121
+ if (client.googleAdsCustomerId) {
122
+ const idResult = validateGoogleAdsId(client.googleAdsCustomerId);
123
+ if (!idResult.valid) {
124
+ errors.push(`${prefix}.googleAdsCustomerId: ${idResult.error}`);
125
+ }
126
+ }
127
+
128
+ // Optional displayName
129
+ if (client.displayName !== undefined) {
130
+ if (typeof client.displayName !== 'string') {
131
+ errors.push(`${prefix}.displayName: must be a string`);
132
+ } else if (client.displayName.length > 100) {
133
+ errors.push(`${prefix}.displayName: must be 1-100 characters`);
134
+ }
135
+ }
136
+
137
+ // Optional website
138
+ if (client.website !== undefined && typeof client.website !== 'string') {
139
+ errors.push(`${prefix}.website: must be a string`);
140
+ }
141
+ }
142
+
143
+ // Optional settings validation
144
+ if (config.settings !== undefined) {
145
+ if (typeof config.settings !== 'object' || config.settings === null) {
146
+ errors.push('settings: must be an object');
147
+ } else if (config.settings.clientsDirectory !== undefined) {
148
+ if (typeof config.settings.clientsDirectory !== 'string') {
149
+ errors.push('settings.clientsDirectory: must be a string');
150
+ }
151
+ }
152
+ }
153
+
154
+ return {
155
+ valid: errors.length === 0,
156
+ errors
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Validate a manifest checksum format
162
+ * @param {string} checksum
163
+ * @returns {boolean}
164
+ */
165
+ export function isValidChecksum(checksum) {
166
+ return typeof checksum === 'string' && /^sha256:[a-f0-9]{64}$/.test(checksum);
167
+ }
168
+
169
+ /**
170
+ * Validate a semver version string
171
+ * @param {string} version
172
+ * @returns {boolean}
173
+ */
174
+ export function isValidVersion(version) {
175
+ return typeof version === 'string' && /^\d+\.\d+\.\d+$/.test(version);
176
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "ppcos",
3
+ "version": "0.3.0",
4
+ "description": "CLI tool to manage Google Ads AI workflow skills and agents for Claude Code",
5
+ "type": "module",
6
+ "bin": {
7
+ "ppcos": "./bin/ppcos.js"
8
+ },
9
+ "scripts": {
10
+ "test": "vitest",
11
+ "test:run": "vitest run"
12
+ },
13
+ "keywords": [],
14
+ "author": "Alfred Simon, Bob Meijer and Miles McNair",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/Pitcocy/ppcos.git"
18
+ },
19
+ "license": "SEE LICENSE IN LICENSE",
20
+ "dependencies": {
21
+ "commander": "^12.0.0",
22
+ "chalk": "^5.3.0",
23
+ "ora": "^8.0.0",
24
+ "unzipper": "^0.12.3"
25
+ },
26
+ "devDependencies": {
27
+ "vitest": "^2.0.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=18.0.0"
31
+ },
32
+ "files": [
33
+ "bin",
34
+ "lib"
35
+ ]
36
+ }