@akropolys/cli 1.6.0 → 1.6.2

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
@@ -209,9 +209,41 @@ async function runInspect(filePath, options) {
209
209
  process.exit(0);
210
210
  }
211
211
 
212
+ // package.json
213
+ var package_default = {
214
+ name: "@akropolys/cli",
215
+ version: "1.6.2",
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
+
212
244
  // src/index.ts
213
245
  var program = new import_commander.Command();
214
- 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);
215
247
  program.command("init").description("Configure the local workspace by generating a default .env file template.").action(async () => {
216
248
  await runInit();
217
249
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akropolys/cli",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
4
4
  "description": "Akropolys CLI — diagnostic, workspace setup, and payload inspection utility.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -10,6 +10,10 @@
10
10
  "bin": {
11
11
  "akropolys": "dist/index.js"
12
12
  },
13
+ "files": [
14
+ "dist",
15
+ "README.md"
16
+ ],
13
17
  "scripts": {
14
18
  "build": "tsup",
15
19
  "dev": "tsup --watch"
@@ -1,84 +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.cloud/v1';
35
-
36
- if (options.verbose) {
37
- console.log(pc.dim(`[Verbose] Site ID: ${siteId || '<not set>'}`));
38
- console.log(pc.dim(`[Verbose] API Token: ${apiToken ? '********' : '<not set>'}`));
39
- console.log(pc.dim(`[Verbose] API URL: ${apiUrl}`));
40
- }
41
-
42
- // 1. Configuration check
43
- if (!siteId) {
44
- console.log(pc.red('❌ Configuration: Site ID is missing. Set NEXT_PUBLIC_AKROPOLYS_SITE_ID in your env.'));
45
- process.exit(1);
46
- }
47
- console.log(pc.green(`✓ Configuration: Site ID detected (${siteId})`));
48
-
49
- if (!apiToken) {
50
- console.log(pc.red('❌ Environment: API Token is missing. Set NEXT_PUBLIC_AKROPOLYS_API_TOKEN in your env.'));
51
- process.exit(1);
52
- }
53
- console.log(pc.green('✓ Environment: API Token detected'));
54
-
55
- // 2. Connectivity check
56
- const start = Date.now();
57
- try {
58
- const res = await fetch(`${apiUrl}/health`, {
59
- method: 'GET',
60
- headers: {
61
- 'X-Akropolys-Token': apiToken,
62
- 'X-Akropolys-Site': siteId,
63
- },
64
- }).catch(err => {
65
- // Throw fetch failures
66
- throw new Error(`Fetch failed: ${err.message}`);
67
- });
68
-
69
- const duration = Date.now() - start;
70
-
71
- if (!res.ok) {
72
- console.log(pc.red(`❌ Connection: API responded with status ${res.status} (Ping: ${duration}ms)`));
73
- process.exit(2);
74
- }
75
-
76
- console.log(pc.green(`✓ Connection: Successfully connected to ${apiUrl} (ping: ${duration}ms)`));
77
-
78
- console.log(pc.bold(pc.green('\nStatus: Healthy (All configuration and connectivity checks passed)')));
79
- process.exit(0);
80
- } catch (err: any) {
81
- console.log(pc.red(`❌ Connection: Unreachable API at ${apiUrl}. Error: ${err.message}`));
82
- process.exit(2);
83
- }
84
- }
@@ -1,38 +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
-
22
- // apiUrl is optional — the SDK defaults to the shared managed backend
23
- // (https://api.akropolys.cloud/v1). Only self-hosted/local dev needs to set it.
24
- const envContent = `NEXT_PUBLIC_AKROPOLYS_SITE_ID=${siteId.trim()}
25
- NEXT_PUBLIC_AKROPOLYS_API_TOKEN=${apiToken.trim()}
26
- `;
27
-
28
- fs.writeFileSync('.env', envContent, 'utf-8');
29
- console.log(pc.dim('\n─────────────────────────'));
30
- console.log(pc.green('✓ Generated .env with configuration parameters.'));
31
- console.log(pc.green('✓ Saved config template.'));
32
- } catch (err: any) {
33
- console.error(pc.red(`\n❌ Error generating configuration: ${err.message}`));
34
- process.exit(1);
35
- } finally {
36
- rl.close();
37
- }
38
- }
@@ -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
- });