@akropolys/cli 1.5.9 → 1.6.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 ADDED
@@ -0,0 +1,45 @@
1
+ # @akropolys/cli
2
+
3
+ Command-line utilities for [Akropolys](https://akropolys.cloud): scaffold local config, check connectivity, and lint catalog payloads before you ingest them.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @akropolys/cli
9
+ # or run on demand
10
+ npx @akropolys/cli <command>
11
+ ```
12
+
13
+ ## Commands
14
+
15
+ ### `akropolys init`
16
+
17
+ Interactively writes an `.env` with your site credentials:
18
+
19
+ ```
20
+ NEXT_PUBLIC_AKROPOLYS_SITE_ID=...
21
+ NEXT_PUBLIC_AKROPOLYS_API_TOKEN=...
22
+ ```
23
+
24
+ ### `akropolys doctor`
25
+
26
+ Verifies your local config and that the API is reachable. Reads `NEXT_PUBLIC_AKROPOLYS_*` or `VITE_AKROPOLYS_*` from `.env` / `.env.local`.
27
+
28
+ ```bash
29
+ akropolys doctor # health check
30
+ akropolys doctor -v # also print the resolved config
31
+ ```
32
+
33
+ ### `akropolys inspect [file]`
34
+
35
+ Statically checks a catalog JSON payload for ingestion issues — missing stable identifier, sparse attributes — before you push it.
36
+
37
+ ```bash
38
+ akropolys inspect catalog.json
39
+ cat catalog.json | akropolys inspect --stdin
40
+ akropolys inspect catalog.json --strict # exit 3 if any warning fires
41
+ ```
42
+
43
+ ## License
44
+
45
+ MIT
package/dist/index.js CHANGED
@@ -43,20 +43,8 @@ async function runInit() {
43
43
  try {
44
44
  const siteId = await question(rl, import_picocolors.default.cyan("? Enter your Akropolys Site ID: "));
45
45
  const apiToken = await question(rl, import_picocolors.default.cyan("? Enter your Akropolys API Token: "));
46
- const apiUrlInput = await question(rl, import_picocolors.default.cyan("? Enter your Akropolys API URL (default: https://api.akropolys.io): "));
47
- const apiUrl = apiUrlInput.trim() || "https://api.akropolys.io";
48
- console.log(import_picocolors.default.cyan("? Select your vertical:"));
49
- console.log(" 1. commerce");
50
- console.log(" 2. property");
51
- console.log(" 3. motor");
52
- const verticalIndex = await question(rl, import_picocolors.default.cyan(" Select (1-3, default: 1): "));
53
- let vertical = "commerce";
54
- if (verticalIndex === "2") vertical = "property";
55
- else if (verticalIndex === "3") vertical = "motor";
56
46
  const envContent = `NEXT_PUBLIC_AKROPOLYS_SITE_ID=${siteId.trim()}
57
47
  NEXT_PUBLIC_AKROPOLYS_API_TOKEN=${apiToken.trim()}
58
- NEXT_PUBLIC_AKROPOLYS_API_URL=${apiUrl.trim()}
59
- NEXT_PUBLIC_AKROPOLYS_VERTICAL=${vertical}
60
48
  `;
61
49
  import_fs.default.writeFileSync(".env", envContent, "utf-8");
62
50
  console.log(import_picocolors.default.dim("\n\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"));
@@ -101,13 +89,11 @@ async function runDoctor(options) {
101
89
  loadEnv();
102
90
  const siteId = process.env.NEXT_PUBLIC_AKROPOLYS_SITE_ID || process.env.VITE_AKROPOLYS_SITE_ID || "";
103
91
  const apiToken = process.env.NEXT_PUBLIC_AKROPOLYS_API_TOKEN || process.env.VITE_AKROPOLYS_API_TOKEN || "";
104
- const apiUrl = process.env.NEXT_PUBLIC_AKROPOLYS_API_URL || process.env.VITE_AKROPOLYS_API_URL || "https://api.akropolys.io";
105
- const vertical = process.env.NEXT_PUBLIC_AKROPOLYS_VERTICAL || process.env.VITE_AKROPOLYS_VERTICAL || "commerce";
92
+ const apiUrl = process.env.NEXT_PUBLIC_AKROPOLYS_API_URL || process.env.VITE_AKROPOLYS_API_URL || "https://api.akropolys.cloud/v1";
106
93
  if (options.verbose) {
107
94
  console.log(import_picocolors2.default.dim(`[Verbose] Site ID: ${siteId || "<not set>"}`));
108
95
  console.log(import_picocolors2.default.dim(`[Verbose] API Token: ${apiToken ? "********" : "<not set>"}`));
109
96
  console.log(import_picocolors2.default.dim(`[Verbose] API URL: ${apiUrl}`));
110
- console.log(import_picocolors2.default.dim(`[Verbose] Vertical: ${vertical}`));
111
97
  }
112
98
  if (!siteId) {
113
99
  console.log(import_picocolors2.default.red("\u274C Configuration: Site ID is missing. Set NEXT_PUBLIC_AKROPOLYS_SITE_ID in your env."));
@@ -136,7 +122,6 @@ async function runDoctor(options) {
136
122
  process.exit(2);
137
123
  }
138
124
  console.log(import_picocolors2.default.green(`\u2713 Connection: Successfully connected to ${apiUrl} (ping: ${duration}ms)`));
139
- console.log(import_picocolors2.default.green(`\u2713 Integration: Site vertical configured as "${vertical}"`));
140
125
  console.log(import_picocolors2.default.bold(import_picocolors2.default.green("\nStatus: Healthy (All configuration and connectivity checks passed)")));
141
126
  process.exit(0);
142
127
  } catch (err) {
@@ -224,9 +209,41 @@ async function runInspect(filePath, options) {
224
209
  process.exit(0);
225
210
  }
226
211
 
212
+ // package.json
213
+ var package_default = {
214
+ name: "@akropolys/cli",
215
+ version: "1.6.1",
216
+ description: "Akropolys CLI \u2014 diagnostic, workspace setup, and payload inspection utility.",
217
+ license: "MIT",
218
+ publishConfig: {
219
+ access: "public"
220
+ },
221
+ main: "dist/index.js",
222
+ bin: {
223
+ akropolys: "dist/index.js"
224
+ },
225
+ files: [
226
+ "dist",
227
+ "README.md"
228
+ ],
229
+ scripts: {
230
+ build: "tsup",
231
+ dev: "tsup --watch"
232
+ },
233
+ dependencies: {
234
+ commander: "^12.0.0",
235
+ picocolors: "^1.0.0"
236
+ },
237
+ devDependencies: {
238
+ tsup: "^8.0.0",
239
+ typescript: "^5.0.0",
240
+ "@types/node": "^20.0.0"
241
+ }
242
+ };
243
+
227
244
  // src/index.ts
228
245
  var program = new import_commander.Command();
229
- program.name("akropolys").description("Akropolys Command Line Tool \u2014 Developer diagnostics, setup helper, and structural inspector.").version("1.0.0");
246
+ program.name("akropolys").description("Akropolys Command Line Tool \u2014 Developer diagnostics, setup helper, and structural inspector.").version(package_default.version);
230
247
  program.command("init").description("Configure the local workspace by generating a default .env file template.").action(async () => {
231
248
  await runInit();
232
249
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akropolys/cli",
3
- "version": "1.5.9",
3
+ "version": "1.6.1",
4
4
  "description": "Akropolys CLI — diagnostic, workspace setup, and payload inspection utility.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -10,10 +10,10 @@
10
10
  "bin": {
11
11
  "akropolys": "dist/index.js"
12
12
  },
13
- "scripts": {
14
- "build": "tsup",
15
- "dev": "tsup --watch"
16
- },
13
+ "files": [
14
+ "dist",
15
+ "README.md"
16
+ ],
17
17
  "dependencies": {
18
18
  "commander": "^12.0.0",
19
19
  "picocolors": "^1.0.0"
@@ -22,5 +22,9 @@
22
22
  "tsup": "^8.0.0",
23
23
  "typescript": "^5.0.0",
24
24
  "@types/node": "^20.0.0"
25
+ },
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "dev": "tsup --watch"
25
29
  }
26
- }
30
+ }
@@ -1,87 +0,0 @@
1
- import fs from 'fs';
2
- import pc from 'picocolors';
3
-
4
- export function loadEnv() {
5
- const envPaths = ['.env', '.env.local'];
6
- for (const envPath of envPaths) {
7
- if (fs.existsSync(envPath)) {
8
- const content = fs.readFileSync(envPath, 'utf-8');
9
- const lines = content.split(/\r?\n/);
10
- for (const line of lines) {
11
- const trimmed = line.trim();
12
- if (!trimmed || trimmed.startsWith('#')) continue;
13
- const eqIdx = trimmed.indexOf('=');
14
- if (eqIdx > 0) {
15
- const key = trimmed.substring(0, eqIdx).trim();
16
- const value = trimmed.substring(eqIdx + 1).trim().replace(/^['"]|['"]$/g, '');
17
- if (key) {
18
- process.env[key] = value;
19
- }
20
- }
21
- }
22
- }
23
- }
24
- }
25
-
26
- export async function runDoctor(options: { verbose?: boolean }) {
27
- console.log(pc.bold('\nAkropolys Doctor'));
28
- console.log(pc.dim('─────────────────────────'));
29
-
30
- loadEnv();
31
-
32
- const siteId = process.env.NEXT_PUBLIC_AKROPOLYS_SITE_ID || process.env.VITE_AKROPOLYS_SITE_ID || '';
33
- const apiToken = process.env.NEXT_PUBLIC_AKROPOLYS_API_TOKEN || process.env.VITE_AKROPOLYS_API_TOKEN || '';
34
- const apiUrl = process.env.NEXT_PUBLIC_AKROPOLYS_API_URL || process.env.VITE_AKROPOLYS_API_URL || 'https://api.akropolys.io';
35
- const vertical = process.env.NEXT_PUBLIC_AKROPOLYS_VERTICAL || process.env.VITE_AKROPOLYS_VERTICAL || 'commerce';
36
-
37
- if (options.verbose) {
38
- console.log(pc.dim(`[Verbose] Site ID: ${siteId || '<not set>'}`));
39
- console.log(pc.dim(`[Verbose] API Token: ${apiToken ? '********' : '<not set>'}`));
40
- console.log(pc.dim(`[Verbose] API URL: ${apiUrl}`));
41
- console.log(pc.dim(`[Verbose] Vertical: ${vertical}`));
42
- }
43
-
44
- // 1. Configuration check
45
- if (!siteId) {
46
- console.log(pc.red('❌ Configuration: Site ID is missing. Set NEXT_PUBLIC_AKROPOLYS_SITE_ID in your env.'));
47
- process.exit(1);
48
- }
49
- console.log(pc.green(`✓ Configuration: Site ID detected (${siteId})`));
50
-
51
- if (!apiToken) {
52
- console.log(pc.red('❌ Environment: API Token is missing. Set NEXT_PUBLIC_AKROPOLYS_API_TOKEN in your env.'));
53
- process.exit(1);
54
- }
55
- console.log(pc.green('✓ Environment: API Token detected'));
56
-
57
- // 2. Connectivity check
58
- const start = Date.now();
59
- try {
60
- const res = await fetch(`${apiUrl}/health`, {
61
- method: 'GET',
62
- headers: {
63
- 'X-Akropolys-Token': apiToken,
64
- 'X-Akropolys-Site': siteId,
65
- },
66
- }).catch(err => {
67
- // Throw fetch failures
68
- throw new Error(`Fetch failed: ${err.message}`);
69
- });
70
-
71
- const duration = Date.now() - start;
72
-
73
- if (!res.ok) {
74
- console.log(pc.red(`❌ Connection: API responded with status ${res.status} (Ping: ${duration}ms)`));
75
- process.exit(2);
76
- }
77
-
78
- console.log(pc.green(`✓ Connection: Successfully connected to ${apiUrl} (ping: ${duration}ms)`));
79
- console.log(pc.green(`✓ Integration: Site vertical configured as "${vertical}"`));
80
-
81
- console.log(pc.bold(pc.green('\nStatus: Healthy (All configuration and connectivity checks passed)')));
82
- process.exit(0);
83
- } catch (err: any) {
84
- console.log(pc.red(`❌ Connection: Unreachable API at ${apiUrl}. Error: ${err.message}`));
85
- process.exit(2);
86
- }
87
- }
@@ -1,49 +0,0 @@
1
- import readline from 'readline';
2
- import fs from 'fs';
3
- import pc from 'picocolors';
4
-
5
- const question = (rl: readline.Interface, query: string): Promise<string> => {
6
- return new Promise((resolve) => rl.question(query, resolve));
7
- };
8
-
9
- export async function runInit() {
10
- console.log(pc.bold(pc.cyan('\nConfiguring Akropolys Workspace...')));
11
- console.log(pc.dim('─────────────────────────'));
12
-
13
- const rl = readline.createInterface({
14
- input: process.stdin,
15
- output: process.stdout,
16
- });
17
-
18
- try {
19
- const siteId = await question(rl, pc.cyan('? Enter your Akropolys Site ID: '));
20
- const apiToken = await question(rl, pc.cyan('? Enter your Akropolys API Token: '));
21
- const apiUrlInput = await question(rl, pc.cyan('? Enter your Akropolys API URL (default: https://api.akropolys.io): '));
22
- const apiUrl = apiUrlInput.trim() || 'https://api.akropolys.io';
23
-
24
- console.log(pc.cyan('? Select your vertical:'));
25
- console.log(' 1. commerce');
26
- console.log(' 2. property');
27
- console.log(' 3. motor');
28
- const verticalIndex = await question(rl, pc.cyan(' Select (1-3, default: 1): '));
29
- let vertical = 'commerce';
30
- if (verticalIndex === '2') vertical = 'property';
31
- else if (verticalIndex === '3') vertical = 'motor';
32
-
33
- const envContent = `NEXT_PUBLIC_AKROPOLYS_SITE_ID=${siteId.trim()}
34
- NEXT_PUBLIC_AKROPOLYS_API_TOKEN=${apiToken.trim()}
35
- NEXT_PUBLIC_AKROPOLYS_API_URL=${apiUrl.trim()}
36
- NEXT_PUBLIC_AKROPOLYS_VERTICAL=${vertical}
37
- `;
38
-
39
- fs.writeFileSync('.env', envContent, 'utf-8');
40
- console.log(pc.dim('\n─────────────────────────'));
41
- console.log(pc.green('✓ Generated .env with configuration parameters.'));
42
- console.log(pc.green('✓ Saved config template.'));
43
- } catch (err: any) {
44
- console.error(pc.red(`\n❌ Error generating configuration: ${err.message}`));
45
- process.exit(1);
46
- } finally {
47
- rl.close();
48
- }
49
- }
@@ -1,100 +0,0 @@
1
- import fs from 'fs';
2
- import pc from 'picocolors';
3
-
4
- function readStdin(): Promise<string> {
5
- return new Promise((resolve, reject) => {
6
- let data = '';
7
- process.stdin.setEncoding('utf-8');
8
- process.stdin.on('readable', () => {
9
- let chunk;
10
- while ((chunk = process.stdin.read()) !== null) {
11
- data += chunk;
12
- }
13
- });
14
- process.stdin.on('end', () => {
15
- resolve(data);
16
- });
17
- process.stdin.on('error', (err) => {
18
- reject(err);
19
- });
20
- });
21
- }
22
-
23
- export async function runInspect(
24
- filePath: string | undefined,
25
- options: { stdin?: boolean; strict?: boolean }
26
- ) {
27
- console.log(pc.bold('\nAkropolys Inspect'));
28
- console.log(pc.dim('─────────────────────────'));
29
-
30
- let rawData = '';
31
-
32
- // 1. Read input
33
- try {
34
- if (options.stdin || (!filePath && !process.stdin.isTTY)) {
35
- rawData = await readStdin();
36
- } else if (filePath) {
37
- if (!fs.existsSync(filePath)) {
38
- console.error(pc.red(`❌ Error: File not found at "${filePath}"`));
39
- process.exit(1);
40
- }
41
- rawData = fs.readFileSync(filePath, 'utf-8');
42
- } else {
43
- console.error(pc.red('❌ Error: Provide a file path or pipe via standard input using --stdin'));
44
- process.exit(1);
45
- }
46
- } catch (err: any) {
47
- console.error(pc.red(`❌ Error reading input: ${err.message}`));
48
- process.exit(1);
49
- }
50
-
51
- // 2. Parse JSON
52
- let items: any[] = [];
53
- try {
54
- const parsed = JSON.parse(rawData.trim());
55
- items = Array.isArray(parsed) ? parsed : [parsed];
56
- } catch (err: any) {
57
- console.error(pc.red(`❌ Error parsing JSON: Invalid JSON structure. ${err.message}`));
58
- process.exit(1);
59
- }
60
-
61
- console.log(pc.cyan(`Parsed ${items.length} catalog items.`));
62
- console.log(pc.dim('\nIngestion Quality Diagnostics:'));
63
-
64
- let warningsCount = 0;
65
-
66
- // 3. Scan items against Registry Rules
67
- items.forEach((item, index) => {
68
- const identifier = item.id || item.productId || item.slug || item.url || item.name || `item at index ${index}`;
69
-
70
- // AP001: Missing Stable Identifier
71
- const hasId = item.id !== undefined && item.id !== null && item.id !== '';
72
- const hasProductId = item.productId !== undefined && item.productId !== null && item.productId !== '';
73
- const hasSlug = item.slug !== undefined && item.slug !== null && item.slug !== '';
74
- const hasUrl = item.url !== undefined && item.url !== null && item.url !== '';
75
- const hasName = item.name !== undefined && item.name !== null && item.name !== '';
76
-
77
- if (!hasId && !hasProductId && !hasSlug && !hasUrl && !hasName) {
78
- console.log(` ${pc.yellow('⚠')} [AP001] Missing Stable Identifier: "${identifier}" ➔ Deduplication & correlation unavailable`);
79
- warningsCount++;
80
- }
81
-
82
- // AP002: Low-Signal Payload (sparse payload: fewer than 2 keys, or only identifier is defined)
83
- const keysCount = Object.keys(item).length;
84
- if (keysCount < 2) {
85
- console.log(` ${pc.yellow('⚠')} [AP002] Low-Signal Payload: "${identifier}" has sparse attributes ➔ Search vector quality reduced`);
86
- warningsCount++;
87
- }
88
- });
89
-
90
- console.log(pc.dim('\n─────────────────────────'));
91
- console.log(pc.bold(`Inspect complete: ${items.length} items checked, ${warningsCount} warnings flagged.`));
92
-
93
- // 4. Exit codes: strict vs non-strict
94
- if (warningsCount > 0 && options.strict) {
95
- console.log(pc.red('Exit code 3: strict mode failed due to warnings.'));
96
- process.exit(3);
97
- }
98
-
99
- process.exit(0);
100
- }
package/src/index.ts DELETED
@@ -1,38 +0,0 @@
1
- import { Command } from 'commander';
2
- import { runInit } from './commands/init';
3
- import { runDoctor } from './commands/doctor';
4
- import { runInspect } from './commands/inspect';
5
-
6
- const program = new Command();
7
-
8
- program
9
- .name('akropolys')
10
- .description('Akropolys Command Line Tool — Developer diagnostics, setup helper, and structural inspector.')
11
- .version('1.0.0');
12
-
13
- program
14
- .command('init')
15
- .description('Configure the local workspace by generating a default .env file template.')
16
- .action(async () => {
17
- await runInit();
18
- });
19
-
20
- program
21
- .command('doctor')
22
- .description('Perform a health check consolidating local configuration values and backend API reachability.')
23
- .option('-v, --verbose', 'Include verbose logs and configuration dumps.')
24
- .action(async (options) => {
25
- await runDoctor(options);
26
- });
27
-
28
- program
29
- .command('inspect')
30
- .description('Statically inspect a catalog payload file or stream against the Akropolys Anti-Pattern Registry.')
31
- .argument('[file]', 'Path to the local catalog JSON file.')
32
- .option('--stdin', 'Force reading data from standard input.')
33
- .option('--strict', 'Fail the process (exit code 3) if any structural registry warnings are triggered.')
34
- .action(async (file, options) => {
35
- await runInspect(file, options);
36
- });
37
-
38
- program.parse(process.argv);
package/tsconfig.json DELETED
@@ -1,13 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "esModuleInterop": true,
7
- "forceConsistentCasingInFileNames": true,
8
- "strict": true,
9
- "skipLibCheck": true,
10
- "outDir": "./dist"
11
- },
12
- "include": ["src/**/*"]
13
- }
package/tsup.config.ts DELETED
@@ -1,11 +0,0 @@
1
- import { defineConfig } from 'tsup';
2
-
3
- export default defineConfig({
4
- entry: ['src/index.ts'],
5
- format: ['cjs'],
6
- clean: true,
7
- dts: false,
8
- banner: {
9
- js: '#!/usr/bin/env node',
10
- },
11
- });