@dk/jolly 0.1.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.
Files changed (44) hide show
  1. package/.env.example +3 -0
  2. package/.mcp.json +7 -0
  3. package/.sisyphus/boulder.json +13 -0
  4. package/.sisyphus/notepads/saleor-agent-cli/decisions.md +11 -0
  5. package/.sisyphus/notepads/saleor-agent-cli/issues.md +6 -0
  6. package/.sisyphus/notepads/saleor-agent-cli/learnings.md +6 -0
  7. package/.sisyphus/plans/saleor-agent-cli.md +600 -0
  8. package/AGENTS.md +46 -0
  9. package/README.md +121 -0
  10. package/bun.lock +65 -0
  11. package/bunfig.toml +8 -0
  12. package/dist/agent.js +259 -0
  13. package/dist/bootstrap.js +492 -0
  14. package/dist/index.js +5798 -0
  15. package/package.json +29 -0
  16. package/src/agents/index.ts +1 -0
  17. package/src/agents/setup.ts +210 -0
  18. package/src/api/auth.ts +21 -0
  19. package/src/api/client.ts +78 -0
  20. package/src/api/endpoints.ts +8 -0
  21. package/src/api/index.ts +4 -0
  22. package/src/cli/agent.ts +26 -0
  23. package/src/cli/bootstrap.ts +24 -0
  24. package/src/cli/commands/agent.ts +40 -0
  25. package/src/cli/commands/app.ts +51 -0
  26. package/src/cli/commands/config.ts +38 -0
  27. package/src/cli/commands/store.ts +65 -0
  28. package/src/cli/index.ts +16 -0
  29. package/src/commands/app.ts +126 -0
  30. package/src/commands/index.ts +1 -0
  31. package/src/commands/store.ts +64 -0
  32. package/src/test/command-handlers.test.ts +227 -0
  33. package/src/test/e2e-flows.test.ts +212 -0
  34. package/src/test/entry-points.test.ts +123 -0
  35. package/src/test/error-handling.test.ts +137 -0
  36. package/src/test/helpers.ts +49 -0
  37. package/src/test/index.ts +1 -0
  38. package/src/test/mocks.ts +132 -0
  39. package/src/test/setup.ts +29 -0
  40. package/src/tui/components.ts +77 -0
  41. package/src/tui/index.ts +3 -0
  42. package/src/tui/renderer.ts +34 -0
  43. package/src/tui/theme.ts +38 -0
  44. package/tsconfig.json +20 -0
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@dk/jolly",
3
+ "version": "0.1.0",
4
+ "description": "Saleor project bootstrapper and agent configurator",
5
+ "type": "module",
6
+ "bin": {
7
+ "jolly": "./dist/index.js",
8
+ "create-saleor-jolly": "./dist/bootstrap.js",
9
+ "init-saleor-jolly": "./dist/agent.js"
10
+ },
11
+ "scripts": {
12
+ "build": "bun build src/cli/index.ts --outdir=dist --target=bun && bun build src/cli/bootstrap.ts --outdir=dist --target=bun && bun build src/cli/agent.ts --outdir=dist --target=bun",
13
+ "dev": "bun --watch src/cli/index.ts",
14
+ "test": "bun test",
15
+ "typecheck": "bun tsc --noEmit"
16
+ },
17
+ "dependencies": {
18
+ "dotenv": "^17.4.0",
19
+ "yargs": "^17.7.2"
20
+ },
21
+ "devDependencies": {
22
+ "@types/yargs": "^17.0.32",
23
+ "bun-types": "^1.3.11",
24
+ "typescript": "^5.4.0"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ }
29
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,210 @@
1
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
2
+ import { spawnSync } from 'child_process';
3
+ import { join } from 'path';
4
+ import { success, error, info } from '../tui/components.js';
5
+
6
+ interface DetectedAgent {
7
+ name: string;
8
+ path: string;
9
+ skillsPath: string;
10
+ }
11
+
12
+ const AGENT_PATHS = {
13
+ opencode: {
14
+ skills: '.agents/skills',
15
+ agentsMd: 'AGENTS.md',
16
+ mcpJson: '.mcp.json',
17
+ },
18
+ claude: {
19
+ skills: '.claude/skills',
20
+ agentsMd: 'CLAUDE.md',
21
+ mcpJson: '.mcp.json',
22
+ },
23
+ openclaw: {
24
+ skills: '.openclaw/skills',
25
+ agentsMd: 'AGENTS.md',
26
+ mcpJson: '.mcp.json',
27
+ },
28
+ nanobot: {
29
+ skills: '.nanobot/skills',
30
+ agentsMd: 'AGENTS.md',
31
+ mcpJson: '.mcp.json',
32
+ },
33
+ };
34
+
35
+ const SKILLS = [
36
+ 'saleor-app',
37
+ 'saleor-configurator',
38
+ 'saleor-core',
39
+ 'saleor-storefront',
40
+ ];
41
+
42
+ export async function setupAgent(projectPath: string = '.'): Promise<void> {
43
+ info('Detecting AI agents...');
44
+
45
+ const detectedAgents = detectAgents(projectPath);
46
+
47
+ if (detectedAgents.length === 0) {
48
+ info('No AI agents detected. Installing skills anyway...');
49
+ installSkills(projectPath, 'opencode');
50
+ createAgentsMd(projectPath);
51
+ createMcpConfig(projectPath);
52
+ return;
53
+ }
54
+
55
+ info(`Detected agents: ${detectedAgents.map(a => a.name).join(', ')}\n`);
56
+
57
+ for (const agent of detectedAgents) {
58
+ info(`Configuring ${agent.name}...`);
59
+ installSkills(projectPath, agent.name as keyof typeof AGENT_PATHS);
60
+ createAgentsMd(projectPath, agent.name as keyof typeof AGENT_PATHS);
61
+ createMcpConfig(projectPath);
62
+ success(` ${agent.name} configured!`);
63
+ }
64
+
65
+ success('\nAgent setup complete!');
66
+ info('\nSkills installed: ' + SKILLS.join(', '));
67
+ info('AGENTS.md created with Saleor conventions');
68
+ info('.mcp.json configured for saleor-mcp');
69
+ }
70
+
71
+ export async function installSkillsCommand(projectPath: string = '.'): Promise<void> {
72
+ info('Installing Saleor agent skills...');
73
+ info('Skills: ' + SKILLS.join(', '));
74
+
75
+ const detectedAgents = detectAgents(projectPath);
76
+
77
+ if (detectedAgents.length === 0) {
78
+ installSkills(projectPath, 'opencode');
79
+ } else {
80
+ for (const agent of detectedAgents) {
81
+ installSkills(projectPath, agent.name as keyof typeof AGENT_PATHS);
82
+ }
83
+ }
84
+
85
+ success('\nSkills installed successfully!');
86
+ }
87
+
88
+ function detectAgents(projectPath: string): DetectedAgent[] {
89
+ const detected: DetectedAgent[] = [];
90
+
91
+ const filesToCheck = [
92
+ { name: 'opencode', file: '.agents/skills' },
93
+ { name: 'claude', file: '.claude' },
94
+ { name: 'openclaw', file: '.openclaw' },
95
+ { name: 'nanobot', file: '.nanobot' },
96
+ ];
97
+
98
+ for (const { name, file } of filesToCheck) {
99
+ const fullPath = join(projectPath, file);
100
+ if (existsSync(fullPath)) {
101
+ detected.push({
102
+ name,
103
+ path: fullPath,
104
+ skillsPath: join(fullPath, 'skills'),
105
+ });
106
+ }
107
+ }
108
+
109
+ return detected;
110
+ }
111
+
112
+ function installSkills(projectPath: string, agentName: keyof typeof AGENT_PATHS): void {
113
+ const agentPaths = AGENT_PATHS[agentName];
114
+ const skillsDir = join(projectPath, agentPaths.skills);
115
+
116
+ mkdirSync(skillsDir, { recursive: true });
117
+
118
+ info(` Installing skills to ${skillsDir}...`);
119
+
120
+ // Skills are installed via git clone from saleor/agent-skills repo
121
+ // Each skill is a subdirectory in the repo
122
+ const skillUrl = 'https://github.com/saleor/agent-skills';
123
+ const baseDir = join(skillsDir, '..');
124
+
125
+ try {
126
+ const result = spawnSync('git', ['clone', '--depth', '1', skillUrl, 'skills'], {
127
+ cwd: baseDir,
128
+ stdio: 'pipe',
129
+ });
130
+
131
+ if (result.status === 0) {
132
+ for (const skill of SKILLS) {
133
+ info(` Installed ${skill}`);
134
+ }
135
+ success(` Skills installed to ${skillsDir}`);
136
+ } else {
137
+ info(` Could not clone skills, skipping...`);
138
+ }
139
+ } catch {
140
+ info(` Could not clone skills, skipping...`);
141
+ }
142
+ }
143
+
144
+ function createAgentsMd(projectPath: string, agentName: keyof typeof AGENT_PATHS = 'opencode'): void {
145
+ const agentsMdContent = `# Saleor Development Guide
146
+
147
+ This project uses Saleor e-commerce platform.
148
+
149
+ ## Commands
150
+
151
+ \`\`\`bash
152
+ # Development
153
+ npm run dev
154
+
155
+ # Build
156
+ npm run build
157
+
158
+ # Test
159
+ npm run test
160
+
161
+ # Lint
162
+ npm run lint
163
+ \`\`\`
164
+
165
+ ## Saleor Cloud
166
+
167
+ - Dashboard: https://cloud.saleor.io
168
+ - Documentation: https://docs.saleor.io
169
+ - API Reference: https://docs.saleor.io/api
170
+
171
+ ## Saleor Skills
172
+
173
+ This project includes Saleor agent skills:
174
+ - saleor-app: App development patterns
175
+ - saleor-configurator: Config as code
176
+ - saleor-core: Backend internals
177
+ - saleor-storefront: Storefront patterns
178
+
179
+ ## MCP Server
180
+
181
+ Configure saleor-mcp for AI agent capabilities:
182
+ \`\`\`json
183
+ {
184
+ "mcpServers": {
185
+ "saleor": {
186
+ "url": "https://mcp.saleor.app"
187
+ }
188
+ }
189
+ }
190
+ \`\`\`
191
+ `;
192
+
193
+ const agentsMdPath = join(projectPath, 'AGENTS.md');
194
+ writeFileSync(agentsMdPath, agentsMdContent);
195
+ info(` Created AGENTS.md`);
196
+ }
197
+
198
+ function createMcpConfig(projectPath: string): void {
199
+ const mcpConfig = {
200
+ mcpServers: {
201
+ saleor: {
202
+ url: 'https://mcp.saleor.app',
203
+ },
204
+ },
205
+ };
206
+
207
+ const mcpPath = join(projectPath, '.mcp.json');
208
+ writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2));
209
+ info(` Created .mcp.json`);
210
+ }
@@ -0,0 +1,21 @@
1
+ import { config } from 'dotenv';
2
+
3
+ config();
4
+
5
+ export function getToken(): string {
6
+ const token = process.env.SALEOR_CLOUD_TOKEN;
7
+ if (!token) {
8
+ throw new Error('SALEOR_CLOUD_TOKEN environment variable is required');
9
+ }
10
+ return token;
11
+ }
12
+
13
+ export function requireToken(): string {
14
+ const token = process.env.SALEOR_CLOUD_TOKEN;
15
+ if (!token) {
16
+ console.error('Error: SALEOR_CLOUD_TOKEN environment variable is required');
17
+ console.error('Get your token at: https://cloud.saleor.io/settings/api-tokens');
18
+ process.exit(1);
19
+ }
20
+ return token;
21
+ }
@@ -0,0 +1,78 @@
1
+ export class SaleorCloudClient {
2
+ private baseUrl = 'https://cloud.saleor.io/api';
3
+ private token: string;
4
+
5
+ constructor(token?: string) {
6
+ this.token = token || process.env.SALEOR_CLOUD_TOKEN || '';
7
+ if (!this.token) {
8
+ throw new Error('SALEOR_CLOUD_TOKEN environment variable is required');
9
+ }
10
+ }
11
+
12
+ async request<T>(endpoint: string, options?: RequestInit): Promise<T> {
13
+ const response = await fetch(`${this.baseUrl}${endpoint}`, {
14
+ ...options,
15
+ headers: {
16
+ 'Authorization': `Bearer ${this.token}`,
17
+ 'Content-Type': 'application/json',
18
+ ...options?.headers,
19
+ },
20
+ });
21
+
22
+ if (!response.ok) {
23
+ throw new Error(`API error: ${response.status} ${response.statusText}`);
24
+ }
25
+
26
+ return response.json();
27
+ }
28
+
29
+ async getStores() {
30
+ return this.request<{ stores: Store[] }>('/stores');
31
+ }
32
+
33
+ async createStore(name: string, region: string = 'us-east-1') {
34
+ return this.request<{ store: Store }>('/stores', {
35
+ method: 'POST',
36
+ body: JSON.stringify({ name, region }),
37
+ });
38
+ }
39
+
40
+ async getEnvironments(storeId: string) {
41
+ return this.request<{ environments: Environment[] }>(`/stores/${storeId}/environments`);
42
+ }
43
+
44
+ async createEnvironment(storeId: string, name: string) {
45
+ return this.request<{ environment: Environment }>(`/stores/${storeId}/environments`, {
46
+ method: 'POST',
47
+ body: JSON.stringify({ name }),
48
+ });
49
+ }
50
+
51
+ async registerApp(environmentId: string, appType: string, name: string) {
52
+ return this.request<{ app: App }>(`/environments/${environmentId}/apps`, {
53
+ method: 'POST',
54
+ body: JSON.stringify({ type: appType, name }),
55
+ });
56
+ }
57
+ }
58
+
59
+ export interface Store {
60
+ id: string;
61
+ name: string;
62
+ region: string;
63
+ created_at: string;
64
+ }
65
+
66
+ export interface Environment {
67
+ id: string;
68
+ name: string;
69
+ store_id: string;
70
+ created_at: string;
71
+ }
72
+
73
+ export interface App {
74
+ id: string;
75
+ name: string;
76
+ type: string;
77
+ environment_id: string;
78
+ }
@@ -0,0 +1,8 @@
1
+ export const endpoints = {
2
+ stores: '/stores',
3
+ store: (id: string) => `/stores/${id}`,
4
+ environments: (storeId: string) => `/stores/${storeId}/environments`,
5
+ environment: (storeId: string, envId: string) => `/stores/${storeId}/environments/${envId}`,
6
+ apps: (environmentId: string) => `/environments/${environmentId}/apps`,
7
+ registerApp: '/apps/register',
8
+ };
@@ -0,0 +1,4 @@
1
+ export { SaleorCloudClient } from './client.js';
2
+ export type { Store, Environment, App } from './client.js';
3
+ export { getToken, requireToken } from './auth.js';
4
+ export { endpoints } from './endpoints.js';
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env bun
2
+ import { setupAgent, installSkillsCommand } from '../agents/setup.js';
3
+
4
+ async function main() {
5
+ const args = process.argv.slice(2);
6
+ const action = args[0] || 'setup';
7
+
8
+ if (action === 'setup' || action === 'install') {
9
+ console.log('Saleor Agent Setup');
10
+ console.log('-------------------\n');
11
+ await setupAgent('.');
12
+ console.log('\nAgent configured successfully!');
13
+ console.log('Restart your AI agent to enable Saleor capabilities.');
14
+ } else if (action === 'skills') {
15
+ await installSkillsCommand('.');
16
+ } else {
17
+ console.error(`Unknown action: ${action}`);
18
+ console.log('Usage: jolly-agent [setup|skills]');
19
+ process.exit(1);
20
+ }
21
+ }
22
+
23
+ main().catch((err) => {
24
+ console.error(`Agent setup failed: ${err}`);
25
+ process.exit(1);
26
+ });
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bun
2
+ import { createStore } from '../commands/store.js';
3
+
4
+ async function main() {
5
+ const args = process.argv.slice(2);
6
+ const projectName = args[0];
7
+
8
+ if (projectName) {
9
+ console.log(`Bootstrapping Saleor project: ${projectName}...`);
10
+ await createStore(projectName, 'us-east-1');
11
+ return;
12
+ }
13
+
14
+ console.log('Saleor Store Bootstrapper');
15
+ console.log('------------------------\n');
16
+ console.log('Usage: npm create @saleor/jolly <project-name>');
17
+ console.log('Or: jolly store create --name <name>');
18
+ console.log('\nFor app scaffolding: jolly app create --name <name> --type <type>');
19
+ }
20
+
21
+ main().catch((err) => {
22
+ console.error(`Bootstrap failed: ${err}`);
23
+ process.exit(1);
24
+ });
@@ -0,0 +1,40 @@
1
+ import type { CommandModule } from 'yargs';
2
+ import { setupAgent, installSkillsCommand } from '../../agents/setup.js';
3
+
4
+ export const agentCommands: CommandModule = {
5
+ command: 'agent <action>',
6
+ describe: 'Configure AI agents for Saleor',
7
+ builder: (yargs) =>
8
+ yargs
9
+ .command({
10
+ command: 'setup',
11
+ describe: 'Setup AI agent with Saleor skills and MCP',
12
+ builder: (yargs) => yargs.option('path', {
13
+ alias: 'p',
14
+ type: 'string',
15
+ description: 'Project path',
16
+ default: '.',
17
+ }),
18
+ handler: async (argv) => {
19
+ await setupAgent(argv.path as string);
20
+ },
21
+ })
22
+ .command({
23
+ command: 'skills',
24
+ describe: 'Install Saleor agent skills',
25
+ builder: (yargs) =>
26
+ yargs.command({
27
+ command: 'install',
28
+ describe: 'Install Saleor skills',
29
+ builder: (yargs) => yargs.option('path', {
30
+ alias: 'p',
31
+ type: 'string',
32
+ description: 'Project path',
33
+ default: '.',
34
+ }),
35
+ handler: async (argv) => {
36
+ await installSkillsCommand(argv.path as string);
37
+ },
38
+ }),
39
+ }),
40
+ };
@@ -0,0 +1,51 @@
1
+ import type { CommandModule } from 'yargs';
2
+ import { createApp } from '../../commands/app.js';
3
+
4
+ type AppType = 'dashboard-extension' | 'payment' | 'webhook';
5
+ type PaymentProvider = 'dummy' | 'stripe';
6
+
7
+ export const appCommands: CommandModule = {
8
+ command: 'app <action>',
9
+ describe: 'Scaffold Saleor apps',
10
+ builder: (yargs) =>
11
+ yargs
12
+ .command({
13
+ command: 'create',
14
+ describe: 'Create a new Saleor app',
15
+ builder: (yargs) =>
16
+ yargs
17
+ .option('name', {
18
+ alias: 'n',
19
+ type: 'string',
20
+ description: 'App name',
21
+ demandOption: true,
22
+ })
23
+ .option('type', {
24
+ alias: 't',
25
+ type: 'string',
26
+ choices: ['dashboard-extension', 'payment', 'webhook'],
27
+ description: 'App type',
28
+ demandOption: true,
29
+ })
30
+ .option('provider', {
31
+ alias: 'p',
32
+ type: 'string',
33
+ choices: ['dummy', 'stripe'],
34
+ description: 'Payment provider (for payment apps)',
35
+ default: 'dummy',
36
+ })
37
+ .option('environment', {
38
+ alias: 'e',
39
+ type: 'string',
40
+ description: 'Environment ID to register app with',
41
+ }),
42
+ handler: async (argv) => {
43
+ await createApp(
44
+ argv.name,
45
+ argv.type as AppType,
46
+ argv.environment,
47
+ argv.provider as PaymentProvider
48
+ );
49
+ },
50
+ }),
51
+ };
@@ -0,0 +1,38 @@
1
+ import type { CommandModule } from 'yargs';
2
+
3
+ export const configCommands: CommandModule = {
4
+ command: 'config <action>',
5
+ describe: 'Manage Saleor configuration',
6
+ builder: (yargs) =>
7
+ yargs
8
+ .command({
9
+ command: 'deploy',
10
+ describe: 'Deploy configuration to a store',
11
+ builder: (yargs) =>
12
+ yargs.option('store', {
13
+ alias: 's',
14
+ type: 'string',
15
+ description: 'Store ID',
16
+ demandOption: true,
17
+ }),
18
+ handler: async (argv) => {
19
+ console.log(`Deploying config to store: ${argv.store}`);
20
+ console.log('Config deployment not yet implemented');
21
+ },
22
+ })
23
+ .command({
24
+ command: 'introspect',
25
+ describe: 'Introspect current store configuration',
26
+ builder: (yargs) =>
27
+ yargs.option('store', {
28
+ alias: 's',
29
+ type: 'string',
30
+ description: 'Store ID',
31
+ demandOption: true,
32
+ }),
33
+ handler: async (argv) => {
34
+ console.log(`Introspecting store: ${argv.store}`);
35
+ console.log('Config introspection not yet implemented');
36
+ },
37
+ }),
38
+ };
@@ -0,0 +1,65 @@
1
+ import type { CommandModule } from 'yargs';
2
+ import { createStore, listStores, createEnvironment } from '../../commands/store.js';
3
+
4
+ export const storeCommands: CommandModule = {
5
+ command: 'store <action>',
6
+ describe: 'Manage Saleor Cloud stores',
7
+ builder: (yargs) =>
8
+ yargs
9
+ .command({
10
+ command: 'create',
11
+ describe: 'Create a new Saleor Cloud store',
12
+ builder: (yargs) =>
13
+ yargs
14
+ .option('name', {
15
+ alias: 'n',
16
+ type: 'string',
17
+ description: 'Store name',
18
+ demandOption: true,
19
+ })
20
+ .option('region', {
21
+ alias: 'r',
22
+ type: 'string',
23
+ description: 'Region (e.g., us-east-1)',
24
+ default: 'us-east-1',
25
+ }),
26
+ handler: async (argv) => {
27
+ await createStore(argv.name, argv.region);
28
+ },
29
+ })
30
+ .command({
31
+ command: 'list',
32
+ describe: 'List your Saleor Cloud stores',
33
+ builder: (yargs) => yargs,
34
+ handler: async () => {
35
+ await listStores();
36
+ },
37
+ })
38
+ .command({
39
+ command: 'env <action>',
40
+ describe: 'Manage store environments',
41
+ builder: (yargs) =>
42
+ yargs
43
+ .command({
44
+ command: 'create',
45
+ describe: 'Create a new environment',
46
+ builder: (yargs) =>
47
+ yargs
48
+ .option('store', {
49
+ alias: 's',
50
+ type: 'string',
51
+ description: 'Store ID',
52
+ demandOption: true,
53
+ })
54
+ .option('name', {
55
+ alias: 'n',
56
+ type: 'string',
57
+ description: 'Environment name',
58
+ demandOption: true,
59
+ }),
60
+ handler: async (argv) => {
61
+ await createEnvironment(argv.store as string, argv.name as string);
62
+ },
63
+ }),
64
+ }),
65
+ };
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bun
2
+ import yargs from 'yargs';
3
+ import { hideBin } from 'yargs/helpers';
4
+ import { storeCommands } from './commands/store.js';
5
+ import { appCommands } from './commands/app.js';
6
+ import { agentCommands } from './commands/agent.js';
7
+ import { configCommands } from './commands/config.js';
8
+
9
+ yargs(hideBin(process.argv))
10
+ .command(storeCommands)
11
+ .command(appCommands)
12
+ .command(agentCommands)
13
+ .command(configCommands)
14
+ .demandCommand(1, 'You must provide a command')
15
+ .strict()
16
+ .parse();