@dk/jolly 0.1.8 → 0.1.10

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 (49) hide show
  1. package/README.md +16 -92
  2. package/bin/jolly +2 -0
  3. package/package.json +27 -24
  4. package/src/index.ts +2095 -0
  5. package/src/lib/cloud-api.ts +527 -0
  6. package/src/lib/env-file.ts +68 -0
  7. package/src/lib/saleor-url.ts +37 -0
  8. package/.env.example +0 -3
  9. package/.mcp.json +0 -7
  10. package/.sisyphus/boulder.json +0 -13
  11. package/.sisyphus/notepads/saleor-agent-cli/decisions.md +0 -11
  12. package/.sisyphus/notepads/saleor-agent-cli/issues.md +0 -6
  13. package/.sisyphus/notepads/saleor-agent-cli/learnings.md +0 -6
  14. package/.sisyphus/plans/saleor-agent-cli.md +0 -600
  15. package/AGENTS.md +0 -46
  16. package/bun.lock +0 -123
  17. package/bunfig.toml +0 -8
  18. package/dist/agent.js +0 -258
  19. package/dist/bootstrap.js +0 -184
  20. package/dist/index.js +0 -722
  21. package/src/agents/index.ts +0 -1
  22. package/src/agents/setup.ts +0 -210
  23. package/src/api/auth.ts +0 -75
  24. package/src/api/client.ts +0 -152
  25. package/src/api/endpoints.ts +0 -8
  26. package/src/api/index.ts +0 -4
  27. package/src/cli/agent.ts +0 -26
  28. package/src/cli/bootstrap.ts +0 -24
  29. package/src/cli/commands/agent.ts +0 -40
  30. package/src/cli/commands/app.ts +0 -61
  31. package/src/cli/commands/config.ts +0 -38
  32. package/src/cli/commands/store.ts +0 -75
  33. package/src/cli/index.ts +0 -16
  34. package/src/commands/app.ts +0 -126
  35. package/src/commands/index.ts +0 -1
  36. package/src/commands/store.ts +0 -64
  37. package/src/test/command-handlers.test.ts +0 -232
  38. package/src/test/e2e-flows.test.ts +0 -231
  39. package/src/test/entry-points.test.ts +0 -126
  40. package/src/test/error-handling.test.ts +0 -137
  41. package/src/test/helpers.ts +0 -49
  42. package/src/test/index.ts +0 -1
  43. package/src/test/mocks.ts +0 -172
  44. package/src/test/setup.ts +0 -29
  45. package/src/tui/components.ts +0 -77
  46. package/src/tui/index.ts +0 -3
  47. package/src/tui/renderer.ts +0 -34
  48. package/src/tui/theme.ts +0 -38
  49. package/tsconfig.json +0 -20
@@ -1 +0,0 @@
1
- export {};
@@ -1,210 +0,0 @@
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
- }
package/src/api/auth.ts DELETED
@@ -1,75 +0,0 @@
1
- import { config } from '@dotenvx/dotenvx';
2
- import { writeFileSync, existsSync, readFileSync } from 'fs';
3
- import { join } from 'path';
4
-
5
- config();
6
-
7
- export function getToken(): string {
8
- const token = process.env.SALEOR_CLOUD_TOKEN;
9
- if (!token) {
10
- throw new Error('SALEOR_CLOUD_TOKEN environment variable is required');
11
- }
12
- return token;
13
- }
14
-
15
- export function requireToken(): string {
16
- const token = process.env.SALEOR_CLOUD_TOKEN;
17
- if (!token) {
18
- console.error('\n🔑 Saleor Cloud Token Required');
19
- console.error(' Get your token at: https://cloud.saleor.io/settings/api-tokens\n');
20
- console.error('Or create a .env file with: SALEOR_CLOUD_TOKEN=your-token');
21
- console.error('\nTo set token: export SALEOR_CLOUD_TOKEN=your-token\n');
22
- process.exit(1);
23
- }
24
- return token;
25
- }
26
-
27
- export async function promptAndSaveToken(): Promise<string> {
28
- console.log('\n🔑 Saleor Cloud Token Required');
29
- console.log(' Get your token at: https://cloud.saleor.io/settings/api-tokens\n');
30
-
31
- const readline = await import('readline');
32
- const rl = readline.createInterface({
33
- input: process.stdin,
34
- output: process.stdout
35
- });
36
-
37
- return new Promise((resolve) => {
38
- rl.question('Enter your SALEOR_CLOUD_TOKEN: ', (answer: string) => {
39
- rl.close();
40
- const token = answer.trim();
41
-
42
- if (!token) {
43
- console.error('Error: Token cannot be empty');
44
- process.exit(1);
45
- }
46
-
47
- const envPath = join(process.cwd(), '.env');
48
- const envLine = `SALEOR_CLOUD_TOKEN=${token}\n`;
49
-
50
- try {
51
- let existingContent = '';
52
- if (existsSync(envPath)) {
53
- existingContent = readFileSync(envPath, 'utf-8');
54
- if (existingContent.includes('SALEOR_CLOUD_TOKEN=')) {
55
- existingContent = existingContent.replace(
56
- /SALEOR_CLOUD_TOKEN=.*\n?/g,
57
- envLine
58
- );
59
- } else {
60
- existingContent += envLine;
61
- }
62
- } else {
63
- existingContent = envLine;
64
- }
65
- writeFileSync(envPath, existingContent);
66
- console.log('\n✅ Token saved to .env file');
67
- } catch {
68
- console.log('\n⚠️ Could not save to .env, token will not persist');
69
- }
70
-
71
- process.env.SALEOR_CLOUD_TOKEN = token;
72
- resolve(token);
73
- });
74
- });
75
- }
package/src/api/client.ts DELETED
@@ -1,152 +0,0 @@
1
- export class SaleorCloudClient {
2
- private baseUrl = 'https://cloud.saleor.io/api/v1';
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 mergedOptions: RequestInit = {
14
- method: options?.method || 'GET',
15
- ...options,
16
- headers: {
17
- 'Authorization': `Token ${this.token}`,
18
- 'Content-Type': 'application/json',
19
- ...options?.headers,
20
- },
21
- };
22
-
23
- const response = await fetch(`${this.baseUrl}${endpoint}`, mergedOptions);
24
-
25
- if (!response.ok) {
26
- const body = await response.text();
27
- const truncatedBody = body.length > 200 ? body.substring(0, 200) + '...' : body;
28
- throw new Error(`API error: ${response.status} ${response.statusText} - ${truncatedBody}`);
29
- }
30
-
31
- return response.json() as T;
32
- }
33
-
34
- // Organizations
35
- async getOrganizations() {
36
- return this.request<{ organizations: Organization[] }>('/organizations');
37
- }
38
-
39
- // Projects
40
- async getProjects(organizationSlug: string) {
41
- return this.request<{ projects: Project[] }>(`/organizations/${organizationSlug}/projects`);
42
- }
43
-
44
- async createProject(organizationSlug: string, name: string, region: string) {
45
- return this.request<{ project: Project }>(`/organizations/${organizationSlug}/projects`, {
46
- method: 'POST',
47
- body: JSON.stringify({ name, region }),
48
- });
49
- }
50
-
51
- // Environments
52
- async getEnvironments(organizationSlug: string, projectSlug: string) {
53
- return this.request<{ environments: Environment[] }>(
54
- `/organizations/${organizationSlug}/projects/${projectSlug}/environments`
55
- );
56
- }
57
-
58
- async createEnvironment(organizationSlug: string, projectSlug: string, name: string, region: string) {
59
- return this.request<{ environment: Environment; task: Task }>(
60
- `/organizations/${organizationSlug}/projects/${projectSlug}/environments`,
61
- {
62
- method: 'POST',
63
- body: JSON.stringify({ name, region }),
64
- }
65
- );
66
- }
67
-
68
- async getEnvironment(organizationSlug: string, projectSlug: string, environmentSlug: string) {
69
- return this.request<Environment>(
70
- `/organizations/${organizationSlug}/projects/${projectSlug}/environments/${environmentSlug}`
71
- );
72
- }
73
-
74
- // Apps
75
- async registerApp(environmentId: string, appType: string, name: string) {
76
- return this.request<{ app: App }>(`/environments/${environmentId}/apps`, {
77
- method: 'POST',
78
- body: JSON.stringify({ type: appType, name }),
79
- });
80
- }
81
-
82
- // Backward compatibility methods
83
- async getStores() {
84
- return this.getOrganizations();
85
- }
86
-
87
- async createStore(name: string, region: string = 'us-east-1') {
88
- // Create organization as "store" - requires org slug
89
- // For now, create in first available org
90
- const { organizations } = await this.getOrganizations();
91
- if (organizations.length === 0) {
92
- throw new Error('No organizations found. Create one at https://cloud.saleor.io');
93
- }
94
- return this.createProject(organizations[0].slug, name, region);
95
- }
96
-
97
- async createEnvironmentFromStore(storeId: string, name: string) {
98
- // storeId is used as organization slug for backward compatibility
99
- const { environments } = await this.getEnvironments(storeId, 'default');
100
- // Create sandbox environment
101
- return this.createEnvironment(storeId, environments.length > 0 ? environments[0].project?.slug || 'default' : 'default', name, 'default');
102
- }
103
- }
104
-
105
- export interface Organization {
106
- slug: string;
107
- name: string;
108
- created: string;
109
- company_name?: string;
110
- owner_email: string;
111
- }
112
-
113
- export interface Project {
114
- slug: string;
115
- name: string;
116
- region: string;
117
- created: string;
118
- billing_period?: { start: string };
119
- sandboxes: { count: number };
120
- }
121
-
122
- export interface Environment {
123
- key: string;
124
- name: string;
125
- domain: string;
126
- service: {
127
- version: string;
128
- type: string;
129
- region: string;
130
- };
131
- created: string;
132
- project: { name: string; slug: string };
133
- }
134
-
135
- export interface Task {
136
- id: string;
137
- status: string;
138
- }
139
-
140
- export interface Store {
141
- id: string;
142
- name: string;
143
- region: string;
144
- created_at: string;
145
- }
146
-
147
- export interface App {
148
- id: string;
149
- name: string;
150
- type: string;
151
- environment_id: string;
152
- }
@@ -1,8 +0,0 @@
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
- };
package/src/api/index.ts DELETED
@@ -1,4 +0,0 @@
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';
package/src/cli/agent.ts DELETED
@@ -1,26 +0,0 @@
1
- #!/usr/bin/env node
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
- });
@@ -1,24 +0,0 @@
1
- #!/usr/bin/env node
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
- });
@@ -1,40 +0,0 @@
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
- };
@@ -1,61 +0,0 @@
1
- import type { CommandModule } from 'yargs';
2
- import { createApp } from '../../commands/app.js';
3
- import { promptAndSaveToken } from '../../api/auth.js';
4
-
5
- type AppType = 'dashboard-extension' | 'payment' | 'webhook';
6
- type PaymentProvider = 'dummy' | 'stripe';
7
-
8
- async function ensureToken() {
9
- if (!process.env.SALEOR_CLOUD_TOKEN) {
10
- await promptAndSaveToken();
11
- }
12
- }
13
-
14
- export const appCommands: CommandModule = {
15
- command: 'app <action>',
16
- describe: 'Scaffold Saleor apps',
17
- builder: (yargs) =>
18
- yargs
19
- .command({
20
- command: 'create',
21
- describe: 'Create a new Saleor app',
22
- builder: (yargs) =>
23
- yargs
24
- .option('name', {
25
- alias: 'n',
26
- type: 'string',
27
- description: 'App name',
28
- demandOption: true,
29
- })
30
- .option('type', {
31
- alias: 't',
32
- type: 'string',
33
- choices: ['dashboard-extension', 'payment', 'webhook'],
34
- description: 'App type',
35
- demandOption: true,
36
- })
37
- .option('provider', {
38
- alias: 'p',
39
- type: 'string',
40
- choices: ['dummy', 'stripe'],
41
- description: 'Payment provider (for payment apps)',
42
- default: 'dummy',
43
- })
44
- .option('environment', {
45
- alias: 'e',
46
- type: 'string',
47
- description: 'Environment ID to register app with',
48
- }),
49
- handler: async (argv) => {
50
- if (argv.environment) {
51
- await ensureToken();
52
- }
53
- await createApp(
54
- argv.name,
55
- argv.type as AppType,
56
- argv.environment,
57
- argv.provider as PaymentProvider
58
- );
59
- },
60
- }),
61
- };
@@ -1,38 +0,0 @@
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
- };