@aiready/cli 0.12.20 ā 0.12.23
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/.turbo/turbo-build.log +8 -8
- package/.turbo/turbo-lint.log +24 -5
- package/.turbo/turbo-test.log +66 -85
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +865 -0
- package/coverage/coverage-final.json +15 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +146 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/src/commands/agent-grounding.ts.html +271 -0
- package/coverage/src/commands/ai-signal-clarity.ts.html +253 -0
- package/coverage/src/commands/change-amplification.ts.html +94 -0
- package/coverage/src/commands/consistency.ts.html +781 -0
- package/coverage/src/commands/context.ts.html +871 -0
- package/coverage/src/commands/deps-health.ts.html +280 -0
- package/coverage/src/commands/doc-drift.ts.html +271 -0
- package/coverage/src/commands/index.html +281 -0
- package/coverage/src/commands/patterns.ts.html +745 -0
- package/coverage/src/commands/scan.ts.html +1393 -0
- package/coverage/src/commands/testability.ts.html +304 -0
- package/coverage/src/commands/upload.ts.html +466 -0
- package/coverage/src/commands/visualize.ts.html +1027 -0
- package/coverage/src/index.html +116 -0
- package/coverage/src/index.ts.html +1372 -0
- package/coverage/src/utils/helpers.ts.html +559 -0
- package/coverage/src/utils/index.html +116 -0
- package/dist/cli.js +249 -15
- package/dist/cli.mjs +247 -13
- package/package.json +13 -12
- package/src/.aiready/aiready-report-20260308-174006.json +29526 -0
- package/src/__tests__/unified.test.ts +95 -0
- package/src/cli.ts +86 -0
- package/src/commands/__tests__/agent-grounding.test.ts +24 -0
- package/src/commands/__tests__/ai-signal-clarity.test.ts +32 -0
- package/src/commands/__tests__/consistency.test.ts +97 -0
- package/src/commands/__tests__/deps-health.test.ts +26 -0
- package/src/commands/__tests__/doc-drift.test.ts +26 -0
- package/src/commands/__tests__/extra-commands.test.ts +177 -0
- package/src/commands/__tests__/scan.test.ts +147 -0
- package/src/commands/__tests__/testability.test.ts +36 -0
- package/src/commands/__tests__/upload.test.ts +51 -0
- package/src/commands/__tests__/visualize.test.ts +82 -0
- package/src/commands/clawmart.ts +162 -0
- package/src/commands/index.ts +8 -0
- package/src/commands/scan.ts +33 -11
- package/src/utils/__tests__/helpers.test.ts +35 -0
- package/vitest.config.ts +20 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { scanAction } from '../scan';
|
|
3
|
+
import * as core from '@aiready/core';
|
|
4
|
+
import * as index from '../../index';
|
|
5
|
+
import * as upload from '../upload';
|
|
6
|
+
import { writeFileSync, readFileSync, existsSync } from 'fs';
|
|
7
|
+
import { Severity } from '@aiready/core';
|
|
8
|
+
|
|
9
|
+
vi.mock('../../index', () => ({
|
|
10
|
+
analyzeUnified: vi.fn(),
|
|
11
|
+
scoreUnified: vi.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock('../upload', () => ({
|
|
15
|
+
uploadAction: vi.fn(),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
vi.mock('@aiready/core', async () => {
|
|
19
|
+
const actual = await vi.importActual('@aiready/core');
|
|
20
|
+
return {
|
|
21
|
+
...actual,
|
|
22
|
+
loadMergedConfig: vi.fn(),
|
|
23
|
+
loadConfig: vi.fn(),
|
|
24
|
+
getRepoMetadata: vi.fn().mockReturnValue({ name: 'test-repo' }),
|
|
25
|
+
handleJSONOutput: vi.fn(),
|
|
26
|
+
handleCLIError: vi.fn(),
|
|
27
|
+
getElapsedTime: vi.fn().mockReturnValue('1.0'),
|
|
28
|
+
resolveOutputPath: vi.fn().mockReturnValue('report.json'),
|
|
29
|
+
formatScore: vi.fn().mockReturnValue('80/100'),
|
|
30
|
+
calculateTokenBudget: vi.fn().mockReturnValue({
|
|
31
|
+
efficiencyRatio: 0.8,
|
|
32
|
+
wastedTokens: {
|
|
33
|
+
total: 100,
|
|
34
|
+
bySource: { duplication: 50, fragmentation: 50 },
|
|
35
|
+
},
|
|
36
|
+
totalContextTokens: 1000,
|
|
37
|
+
}),
|
|
38
|
+
calculateBusinessROI: vi.fn().mockReturnValue({
|
|
39
|
+
monthlySavings: 500,
|
|
40
|
+
productivityGainHours: 20,
|
|
41
|
+
annualValue: 6000,
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
vi.mock('fs', () => ({
|
|
47
|
+
writeFileSync: vi.fn(),
|
|
48
|
+
readFileSync: vi.fn(),
|
|
49
|
+
existsSync: vi.fn().mockReturnValue(true),
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
describe('Scan CLI Action', () => {
|
|
53
|
+
let consoleSpy: any;
|
|
54
|
+
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
vi.clearAllMocks();
|
|
57
|
+
consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
58
|
+
vi.mocked(core.loadMergedConfig).mockResolvedValue({
|
|
59
|
+
tools: ['pattern-detect'],
|
|
60
|
+
output: { format: 'console' },
|
|
61
|
+
});
|
|
62
|
+
vi.mocked(index.analyzeUnified).mockResolvedValue({
|
|
63
|
+
summary: {
|
|
64
|
+
totalIssues: 5,
|
|
65
|
+
toolsRun: ['pattern-detect'],
|
|
66
|
+
totalFiles: 10,
|
|
67
|
+
executionTime: 1000,
|
|
68
|
+
},
|
|
69
|
+
'pattern-detect': {
|
|
70
|
+
results: [
|
|
71
|
+
{
|
|
72
|
+
fileName: 'f1.ts',
|
|
73
|
+
issues: [
|
|
74
|
+
{ severity: Severity.Critical },
|
|
75
|
+
{ severity: Severity.Major },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
} as any);
|
|
81
|
+
vi.mocked(index.scoreUnified).mockResolvedValue({
|
|
82
|
+
overall: 80,
|
|
83
|
+
breakdown: [
|
|
84
|
+
{
|
|
85
|
+
toolName: 'pattern-detect',
|
|
86
|
+
score: 80,
|
|
87
|
+
tokenBudget: {
|
|
88
|
+
totalContextTokens: 1000,
|
|
89
|
+
wastedTokens: { bySource: { duplication: 50, fragmentation: 50 } },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
} as any);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('runs standard scan with scoring', async () => {
|
|
97
|
+
await scanAction('.', { score: true });
|
|
98
|
+
expect(index.analyzeUnified).toHaveBeenCalled();
|
|
99
|
+
expect(index.scoreUnified).toHaveBeenCalled();
|
|
100
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
101
|
+
expect.stringContaining('AI Readiness Overall Score')
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('handles profiles correctly', async () => {
|
|
106
|
+
await scanAction('.', { profile: 'agentic' });
|
|
107
|
+
expect(core.loadMergedConfig).toHaveBeenCalled();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('compares with previous report', async () => {
|
|
111
|
+
vi.mocked(readFileSync).mockReturnValue(
|
|
112
|
+
JSON.stringify({ scoring: { overall: 70 } })
|
|
113
|
+
);
|
|
114
|
+
await scanAction('.', { compareTo: 'prev.json', score: true });
|
|
115
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
116
|
+
expect.stringContaining('Trend: +10')
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('handles CI failure on critical issues', async () => {
|
|
121
|
+
const exitSpy = vi
|
|
122
|
+
.spyOn(process, 'exit')
|
|
123
|
+
.mockImplementation((() => {}) as any);
|
|
124
|
+
|
|
125
|
+
await scanAction('.', { ci: true, failOn: 'critical', score: true });
|
|
126
|
+
|
|
127
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
128
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
129
|
+
expect.stringContaining('PR BLOCKED')
|
|
130
|
+
);
|
|
131
|
+
exitSpy.mockRestore();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('handles upload flag', async () => {
|
|
135
|
+
await scanAction('.', { upload: true, apiKey: 'test-key' });
|
|
136
|
+
expect(upload.uploadAction).toHaveBeenCalled();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('supports JSON output format', async () => {
|
|
140
|
+
vi.mocked(core.loadMergedConfig).mockResolvedValue({
|
|
141
|
+
tools: ['pattern-detect'],
|
|
142
|
+
output: { format: 'json', file: 'out.json' },
|
|
143
|
+
});
|
|
144
|
+
await scanAction('.', {});
|
|
145
|
+
expect(core.handleJSONOutput).toHaveBeenCalled();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { testabilityAction } from '../testability';
|
|
3
|
+
|
|
4
|
+
vi.mock('@aiready/testability', () => ({
|
|
5
|
+
analyzeTestability: vi.fn().mockResolvedValue({
|
|
6
|
+
summary: {
|
|
7
|
+
score: 80,
|
|
8
|
+
rating: 'good',
|
|
9
|
+
aiChangeSafetyRating: 'safe',
|
|
10
|
+
coverageRatio: 0.5,
|
|
11
|
+
},
|
|
12
|
+
rawData: { testFiles: 5, sourceFiles: 10 },
|
|
13
|
+
}),
|
|
14
|
+
calculateTestabilityScore: vi.fn().mockReturnValue({ score: 80 }),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
vi.mock('@aiready/core', () => ({
|
|
18
|
+
loadConfig: vi.fn().mockResolvedValue({}),
|
|
19
|
+
mergeConfigWithDefaults: vi
|
|
20
|
+
.fn()
|
|
21
|
+
.mockImplementation((c, d) => ({ ...d, ...c })),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
describe('Testability CLI Action', () => {
|
|
25
|
+
it('should run analysis and return scoring in json mode', async () => {
|
|
26
|
+
const result = await testabilityAction('.', { output: 'json' });
|
|
27
|
+
expect(result?.score).toBe(80);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should run analysis and print to console in default mode', async () => {
|
|
31
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
32
|
+
await testabilityAction('.', { output: 'console' });
|
|
33
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
34
|
+
consoleSpy.mockRestore();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { uploadAction } from '../upload';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
|
|
5
|
+
vi.mock('fs', () => ({
|
|
6
|
+
default: {
|
|
7
|
+
existsSync: vi.fn().mockReturnValue(true),
|
|
8
|
+
readFileSync: vi.fn().mockReturnValue('{"test": true}'),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
vi.mock('@aiready/core', () => ({
|
|
13
|
+
handleCLIError: vi.fn(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
describe('Upload CLI Action', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
vi.stubGlobal(
|
|
19
|
+
'fetch',
|
|
20
|
+
vi.fn().mockResolvedValue({
|
|
21
|
+
ok: true,
|
|
22
|
+
headers: { get: () => 'application/json' },
|
|
23
|
+
json: () =>
|
|
24
|
+
Promise.resolve({
|
|
25
|
+
success: true,
|
|
26
|
+
analysis: { id: '123', aiScore: 80 },
|
|
27
|
+
}),
|
|
28
|
+
})
|
|
29
|
+
);
|
|
30
|
+
vi.stubGlobal('process', {
|
|
31
|
+
...process,
|
|
32
|
+
exit: vi.fn(),
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should upload report successfully', async () => {
|
|
37
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
38
|
+
await uploadAction('report.json', { apiKey: 'test-key' });
|
|
39
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
40
|
+
expect.stringContaining('Upload successful')
|
|
41
|
+
);
|
|
42
|
+
consoleSpy.mockRestore();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should fail if API key is missing', async () => {
|
|
46
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
47
|
+
await uploadAction('report.json', {});
|
|
48
|
+
expect(process.exit).toHaveBeenCalledWith(1);
|
|
49
|
+
consoleSpy.mockRestore();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { visualizeAction } from '../visualize';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as core from '@aiready/core';
|
|
5
|
+
import * as helpers from '../../utils/helpers';
|
|
6
|
+
import { spawn } from 'child_process';
|
|
7
|
+
|
|
8
|
+
vi.mock('fs', async () => {
|
|
9
|
+
const actual = await vi.importActual('fs');
|
|
10
|
+
return {
|
|
11
|
+
...actual,
|
|
12
|
+
readFileSync: vi.fn(),
|
|
13
|
+
existsSync: vi.fn(),
|
|
14
|
+
writeFileSync: vi.fn(),
|
|
15
|
+
copyFileSync: vi.fn(),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
vi.mock('child_process', () => ({
|
|
20
|
+
spawn: vi.fn().mockReturnValue({ on: vi.fn(), kill: vi.fn() }),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
vi.mock('@aiready/visualizer/graph', () => ({
|
|
24
|
+
GraphBuilder: {
|
|
25
|
+
buildFromReport: vi.fn().mockReturnValue({ nodes: [], edges: [] }),
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
vi.mock('@aiready/core', () => ({
|
|
30
|
+
handleCLIError: vi.fn(),
|
|
31
|
+
generateHTML: vi.fn().mockReturnValue('<html></html>'),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
vi.mock('../../utils/helpers', () => ({
|
|
35
|
+
findLatestScanReport: vi.fn(),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
describe('Visualize CLI Action', () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
vi.clearAllMocks();
|
|
41
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(true);
|
|
42
|
+
vi.spyOn(fs, 'readFileSync').mockReturnValue(
|
|
43
|
+
JSON.stringify({ scoring: { overall: 80 } })
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should generate HTML from specified report', async () => {
|
|
48
|
+
await visualizeAction('.', { report: 'report.json' });
|
|
49
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
50
|
+
expect.stringContaining('visualization.html'),
|
|
51
|
+
'<html></html>',
|
|
52
|
+
'utf8'
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should find latest report if none specified', async () => {
|
|
57
|
+
vi.spyOn(helpers, 'findLatestScanReport').mockReturnValue('latest.json');
|
|
58
|
+
await visualizeAction('.', {});
|
|
59
|
+
expect(fs.readFileSync).toHaveBeenCalledWith(
|
|
60
|
+
expect.stringContaining('latest.json'),
|
|
61
|
+
'utf8'
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should handle missing reports', async () => {
|
|
66
|
+
vi.spyOn(fs, 'existsSync').mockReturnValue(false);
|
|
67
|
+
vi.spyOn(helpers, 'findLatestScanReport').mockReturnValue(null);
|
|
68
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
69
|
+
|
|
70
|
+
await visualizeAction('.', { report: 'missing.json' });
|
|
71
|
+
|
|
72
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
73
|
+
expect.stringContaining('No AI readiness report found')
|
|
74
|
+
);
|
|
75
|
+
consoleSpy.mockRestore();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should attempt to open visualization if requested', async () => {
|
|
79
|
+
await visualizeAction('.', { report: 'report.json', open: true });
|
|
80
|
+
expect(spawn).toHaveBeenCalled();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import { resolve as resolvePath } from 'path';
|
|
4
|
+
import {
|
|
5
|
+
ClawMartClient,
|
|
6
|
+
ClawMartListing,
|
|
7
|
+
DownloadPackageResponse,
|
|
8
|
+
} from '@aiready/clawmart';
|
|
9
|
+
|
|
10
|
+
function getClient(options: any) {
|
|
11
|
+
const apiKey = options.apiKey || process.env.CLAWMART_API_KEY;
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
console.error(chalk.red('ā ClawMart API Key is required.'));
|
|
14
|
+
console.log(
|
|
15
|
+
chalk.dim(
|
|
16
|
+
' Set CLAWMART_API_KEY environment variable or use --api-key flag.'
|
|
17
|
+
)
|
|
18
|
+
);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
return new ClawMartClient(apiKey, options.server);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function clawmartMeAction(options: any) {
|
|
25
|
+
const client = getClient(options);
|
|
26
|
+
try {
|
|
27
|
+
const me = await client.getMe();
|
|
28
|
+
console.log(chalk.blue('\nš¤ ClawMart Profile:'));
|
|
29
|
+
console.log(` Name: ${chalk.bold(me.name)}`);
|
|
30
|
+
console.log(` Email: ${me.email}`);
|
|
31
|
+
console.log(` Role: ${me.isCreator ? 'Creator' : 'User'}`);
|
|
32
|
+
console.log(
|
|
33
|
+
` Sub: ${me.subscriptionActive ? chalk.green('Active') : chalk.red('Inactive')}`
|
|
34
|
+
);
|
|
35
|
+
} catch (error: any) {
|
|
36
|
+
console.error(chalk.red(`ā Failed to fetch profile: ${error.message}`));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function clawmartListingsAction(options: any) {
|
|
41
|
+
const client = getClient(options);
|
|
42
|
+
try {
|
|
43
|
+
let listings;
|
|
44
|
+
if (options.query) {
|
|
45
|
+
listings = await client.searchListings(
|
|
46
|
+
options.query,
|
|
47
|
+
options.type,
|
|
48
|
+
options.limit
|
|
49
|
+
);
|
|
50
|
+
} else {
|
|
51
|
+
listings = await client.getListings();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (listings.length === 0) {
|
|
55
|
+
console.log(chalk.yellow('\nš No listings found.'));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(chalk.blue(`\nš ClawMart Listings (${listings.length}):`));
|
|
60
|
+
listings.forEach((l: ClawMartListing) => {
|
|
61
|
+
const status = l.published
|
|
62
|
+
? chalk.green('Published')
|
|
63
|
+
: chalk.yellow('Draft');
|
|
64
|
+
console.log(` - ${chalk.bold(l.name)} (${chalk.dim(l.id)})`);
|
|
65
|
+
console.log(` ${chalk.italic(l.tagline)}`);
|
|
66
|
+
console.log(
|
|
67
|
+
` Price: $${l.price} | Type: ${l.productType} | Status: ${status}`
|
|
68
|
+
);
|
|
69
|
+
console.log('');
|
|
70
|
+
});
|
|
71
|
+
} catch (error: any) {
|
|
72
|
+
console.error(chalk.red(`ā Failed to fetch listings: ${error.message}`));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function clawmartCreateAction(options: any) {
|
|
77
|
+
const client = getClient(options);
|
|
78
|
+
try {
|
|
79
|
+
const data = {
|
|
80
|
+
name: options.name,
|
|
81
|
+
tagline: options.tagline,
|
|
82
|
+
about: options.about || '',
|
|
83
|
+
category: options.category || 'Utility',
|
|
84
|
+
capabilities: options.capabilities ? options.capabilities.split(',') : [],
|
|
85
|
+
price: parseFloat(options.price) || 0,
|
|
86
|
+
productType: options.type as 'skill' | 'persona',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const listing = await client.createListing(data);
|
|
90
|
+
console.log(chalk.green(`\nā
Listing created successfully!`));
|
|
91
|
+
console.log(` ID: ${listing.id}`);
|
|
92
|
+
console.log(` Name: ${listing.name}`);
|
|
93
|
+
} catch (error: any) {
|
|
94
|
+
console.error(chalk.red(`ā Failed to create listing: ${error.message}`));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function clawmartUploadAction(
|
|
99
|
+
id: string,
|
|
100
|
+
files: string[],
|
|
101
|
+
options: any
|
|
102
|
+
) {
|
|
103
|
+
const client = getClient(options);
|
|
104
|
+
try {
|
|
105
|
+
const fileData = files.map((f) => {
|
|
106
|
+
const path = resolvePath(process.cwd(), f);
|
|
107
|
+
if (!fs.existsSync(path)) {
|
|
108
|
+
throw new Error(`File not found: ${f}`);
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
path: f,
|
|
112
|
+
content: fs.readFileSync(path, 'utf-8'),
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await client.uploadVersion(id, fileData);
|
|
117
|
+
console.log(
|
|
118
|
+
chalk.green(`\nā
New version uploaded successfully to listing ${id}!`)
|
|
119
|
+
);
|
|
120
|
+
} catch (error: any) {
|
|
121
|
+
console.error(chalk.red(`ā Failed to upload version: ${error.message}`));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function clawmartDownloadAction(idOrSlug: string, options: any) {
|
|
126
|
+
const client = getClient(options);
|
|
127
|
+
try {
|
|
128
|
+
const pkg = await client.downloadPackage(idOrSlug);
|
|
129
|
+
const outDir = options.outDir || `./clawmart-${pkg.slug}`;
|
|
130
|
+
|
|
131
|
+
if (!fs.existsSync(outDir)) {
|
|
132
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
pkg.files.forEach((f: DownloadPackageResponse['files'][number]) => {
|
|
136
|
+
const filePath = resolvePath(outDir, f.path);
|
|
137
|
+
const dir = resolvePath(filePath, '..');
|
|
138
|
+
if (!fs.existsSync(dir)) {
|
|
139
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
140
|
+
}
|
|
141
|
+
fs.writeFileSync(filePath, f.content);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
console.log(
|
|
145
|
+
chalk.green(`\nā
Package ${idOrSlug} downloaded to ${outDir}`)
|
|
146
|
+
);
|
|
147
|
+
} catch (error: any) {
|
|
148
|
+
console.error(chalk.red(`ā Failed to download package: ${error.message}`));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export const clawmartHelpText = `
|
|
153
|
+
EXAMPLES:
|
|
154
|
+
$ aiready clawmart me
|
|
155
|
+
$ aiready clawmart listings --query "marketing"
|
|
156
|
+
$ aiready clawmart create --name "SEO Booster" --tagline "Boost your SEO" --type skill --price 10
|
|
157
|
+
$ aiready clawmart upload <listing-id> SKILL.md rules/
|
|
158
|
+
$ aiready clawmart download <listing-id-or-slug> --outDir ./my-skill
|
|
159
|
+
|
|
160
|
+
ENVIRONMENT VARIABLES:
|
|
161
|
+
CLAWMART_API_KEY Your ClawMart creator API key
|
|
162
|
+
`;
|
package/src/commands/index.ts
CHANGED
|
@@ -16,3 +16,11 @@ export { agentGroundingAction } from './agent-grounding';
|
|
|
16
16
|
export { testabilityAction } from './testability';
|
|
17
17
|
export { changeAmplificationAction } from './change-amplification';
|
|
18
18
|
export { uploadAction, uploadHelpText } from './upload';
|
|
19
|
+
export {
|
|
20
|
+
clawmartMeAction,
|
|
21
|
+
clawmartListingsAction,
|
|
22
|
+
clawmartCreateAction,
|
|
23
|
+
clawmartUploadAction,
|
|
24
|
+
clawmartDownloadAction,
|
|
25
|
+
clawmartHelpText,
|
|
26
|
+
} from './clawmart';
|
package/src/commands/scan.ts
CHANGED
|
@@ -262,28 +262,50 @@ export async function scanAction(directory: string, options: ScanOptions) {
|
|
|
262
262
|
wastedTokens: {
|
|
263
263
|
duplication: totalWastedDuplication,
|
|
264
264
|
fragmentation: totalWastedFragmentation,
|
|
265
|
-
chattiness: 0,
|
|
265
|
+
chattiness: totalContext * 0.1, // Default chattiness
|
|
266
266
|
},
|
|
267
267
|
});
|
|
268
|
-
const modelPreset = getModelPreset(options.model || 'claude-4.6');
|
|
269
|
-
const costEstimate = estimateCostFromBudget(unifiedBudget, modelPreset);
|
|
270
268
|
|
|
271
|
-
|
|
269
|
+
const allIssues: any[] = [];
|
|
270
|
+
for (const toolId of results.summary.toolsRun) {
|
|
271
|
+
if (results[toolId]?.results) {
|
|
272
|
+
results[toolId].results.forEach((fileRes: any) => {
|
|
273
|
+
if (fileRes.issues) {
|
|
274
|
+
allIssues.push(...fileRes.issues);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const modelId = options.model || 'claude-3-5-sonnet';
|
|
281
|
+
const roi = (await import('@aiready/core')).calculateBusinessROI({
|
|
282
|
+
tokenWaste: unifiedBudget.wastedTokens.total,
|
|
283
|
+
issues: allIssues,
|
|
284
|
+
modelId: modelId,
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
console.log(chalk.bold('\nš° Business Impact Analysis (Monthly)'));
|
|
272
288
|
console.log(
|
|
273
|
-
`
|
|
289
|
+
` Potential Savings: ${chalk.green(chalk.bold('$' + roi.monthlySavings.toLocaleString()))}`
|
|
274
290
|
);
|
|
275
291
|
console.log(
|
|
276
|
-
`
|
|
292
|
+
` Productivity Gain: ${chalk.cyan(chalk.bold(roi.productivityGainHours + 'h'))} (est. dev time)`
|
|
277
293
|
);
|
|
278
294
|
console.log(
|
|
279
|
-
`
|
|
295
|
+
` Context Efficiency: ${chalk.yellow((unifiedBudget.efficiencyRatio * 100).toFixed(0) + '%')}`
|
|
296
|
+
);
|
|
297
|
+
console.log(
|
|
298
|
+
` Annual Value: ${chalk.bold('$' + roi.annualValue.toLocaleString())} (ROI Prediction)`
|
|
280
299
|
);
|
|
281
300
|
|
|
282
|
-
(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
301
|
+
(results.summary as any).businessImpact = {
|
|
302
|
+
estimatedMonthlyWaste: roi.monthlySavings,
|
|
303
|
+
potentialSavings: roi.monthlySavings,
|
|
304
|
+
productivityHours: roi.productivityGainHours,
|
|
286
305
|
};
|
|
306
|
+
|
|
307
|
+
(scoringResult as any).tokenBudget = unifiedBudget;
|
|
308
|
+
(scoringResult as any).businessROI = roi;
|
|
287
309
|
}
|
|
288
310
|
|
|
289
311
|
if (scoringResult.breakdown) {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
getReportTimestamp,
|
|
4
|
+
truncateArray,
|
|
5
|
+
generateMarkdownReport,
|
|
6
|
+
} from '../helpers';
|
|
7
|
+
|
|
8
|
+
describe('CLI Helpers', () => {
|
|
9
|
+
it('should generate a valid timestamp', () => {
|
|
10
|
+
const ts = getReportTimestamp();
|
|
11
|
+
expect(ts).toMatch(/^\d{8}-\d{6}$/);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should truncate arrays correctly', () => {
|
|
15
|
+
const arr = [1, 2, 3, 4, 5];
|
|
16
|
+
expect(truncateArray(arr, 3)).toContain('+2 more');
|
|
17
|
+
expect(truncateArray(arr, 10)).toBe('1, 2, 3, 4, 5');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should generate markdown report', () => {
|
|
21
|
+
const report = {
|
|
22
|
+
summary: {
|
|
23
|
+
filesAnalyzed: 10,
|
|
24
|
+
totalIssues: 5,
|
|
25
|
+
namingIssues: 2,
|
|
26
|
+
patternIssues: 3,
|
|
27
|
+
},
|
|
28
|
+
recommendations: ['Fix naming'],
|
|
29
|
+
};
|
|
30
|
+
const md = generateMarkdownReport(report, '1.5');
|
|
31
|
+
expect(md).toContain('# Consistency Analysis Report');
|
|
32
|
+
expect(md).toContain('**Files Analyzed:** 10');
|
|
33
|
+
expect(md).toContain('Fix naming');
|
|
34
|
+
});
|
|
35
|
+
});
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
test: {
|
|
6
|
+
globals: true,
|
|
7
|
+
environment: 'node',
|
|
8
|
+
alias: {
|
|
9
|
+
'@aiready/core': resolve(__dirname, '../core/src/index.ts'),
|
|
10
|
+
'@aiready/pattern-detect': resolve(
|
|
11
|
+
__dirname,
|
|
12
|
+
'../pattern-detect/src/index.ts'
|
|
13
|
+
),
|
|
14
|
+
'@aiready/context-analyzer': resolve(
|
|
15
|
+
__dirname,
|
|
16
|
+
'../context-analyzer/src/index.ts'
|
|
17
|
+
),
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|