durjoyai-cli 1.2.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.
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ // Load .env if present (local development only)
6
+ try {
7
+ require('dotenv').config({ path: require('path').resolve(__dirname, '..', '.env') });
8
+ } catch {
9
+ // dotenv not installed — using defaults
10
+ }
11
+
12
+ // Require Node 18+
13
+ const [major] = process.versions.node.split('.').map(Number);
14
+ if (major < 18) {
15
+ console.error('durjoyai-cli requires Node.js 18 or higher.');
16
+ console.error(`You are running Node.js ${process.versions.node}`);
17
+ process.exit(1);
18
+ }
19
+
20
+ require('../src/index.js');
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "durjoyai-cli",
3
+ "version": "1.2.0",
4
+ "description": "Set up Claude Code with DurjoyAI in one command",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "durjoyai-cli": "bin/durjoyai-cli.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src"
12
+ ],
13
+ "scripts": {
14
+ "start": "node bin/durjoyai-cli.js"
15
+ },
16
+ "dependencies": {
17
+ "inquirer": "^9.3.7",
18
+ "open": "^10.1.0"
19
+ },
20
+ "devDependencies": {
21
+ "dotenv": "^17.4.1"
22
+ },
23
+ "engines": {
24
+ "node": ">=18.0.0"
25
+ },
26
+ "keywords": [
27
+ "durjoyai",
28
+ "claude",
29
+ "claude-code",
30
+ "cli",
31
+ "ai"
32
+ ],
33
+ "license": "MIT"
34
+ }
package/src/api.js ADDED
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // API module
5
+ // ---------------------------------------------------------------------------
6
+ // With the device-code flow, credentials are returned directly from the poll
7
+ // endpoint. This module is kept minimal for any future API needs.
8
+
9
+ const { DURJOYAI_API_BASE } = require('./auth');
10
+
11
+ module.exports = { DURJOYAI_API_BASE };
package/src/auth.js ADDED
@@ -0,0 +1,171 @@
1
+ 'use strict';
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Constants
5
+ // ---------------------------------------------------------------------------
6
+ const DURJOYAI_API_BASE = process.env.DURJOYAI_API_URL || 'https://cli.durjoyai.com';
7
+ const POLL_INTERVAL_MS = 3000;
8
+ const POLL_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes (matches server code expiry)
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Helpers
12
+ // ---------------------------------------------------------------------------
13
+
14
+ /**
15
+ * Opens the user's default browser to a URL.
16
+ */
17
+ async function openBrowser(url) {
18
+ const { default: open } = await import('open');
19
+ await open(url);
20
+ }
21
+
22
+ /**
23
+ * Simple HTTPS request helper using built-in modules.
24
+ * Returns { status, body }.
25
+ */
26
+ function request(url, options = {}) {
27
+ return new Promise((resolve, reject) => {
28
+ const parsed = new URL(url);
29
+ const lib = parsed.protocol === 'https:' ? require('https') : require('http');
30
+
31
+ const body = options.body ? JSON.stringify(options.body) : null;
32
+ const headers = {
33
+ 'Content-Type': 'application/json',
34
+ 'Accept': 'application/json',
35
+ ...(options.headers || {}),
36
+ };
37
+ if (body) headers['Content-Length'] = Buffer.byteLength(body);
38
+
39
+ const opts = {
40
+ hostname: parsed.hostname,
41
+ port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
42
+ path: parsed.pathname + parsed.search,
43
+ method: options.method || 'GET',
44
+ headers,
45
+ };
46
+
47
+ const req = lib.request(opts, (res) => {
48
+ let data = '';
49
+ res.on('data', (chunk) => (data += chunk));
50
+ res.on('end', () => {
51
+ try {
52
+ resolve({ status: res.statusCode, body: JSON.parse(data) });
53
+ } catch {
54
+ resolve({ status: res.statusCode, body: data });
55
+ }
56
+ });
57
+ });
58
+
59
+ req.on('error', reject);
60
+ if (body) req.write(body);
61
+ req.end();
62
+ });
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Device Code Flow
67
+ // ---------------------------------------------------------------------------
68
+
69
+ /**
70
+ * Requests a new device code from the backend.
71
+ * Returns { code, expiresAt, verificationUrl }.
72
+ */
73
+ async function requestDeviceCode() {
74
+ const { status, body } = await request(
75
+ `${DURJOYAI_API_BASE}/api/cli-auth/device-code`,
76
+ { method: 'POST' }
77
+ );
78
+
79
+ if (status !== 200) {
80
+ throw new Error(
81
+ (body && body.error) || `Failed to get device code (HTTP ${status})`
82
+ );
83
+ }
84
+
85
+ return body;
86
+ }
87
+
88
+ /**
89
+ * Polls the backend until the device code is approved, expired, or times out.
90
+ * Returns the credentials object on success.
91
+ */
92
+ async function pollForApproval(code) {
93
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
94
+
95
+ while (Date.now() < deadline) {
96
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
97
+
98
+ try {
99
+ const { status, body } = await request(
100
+ `${DURJOYAI_API_BASE}/api/cli-auth/poll`,
101
+ { method: 'POST', body: { code } }
102
+ );
103
+
104
+ if (status !== 200) {
105
+ throw new Error(
106
+ (body && body.error) || `Poll failed (HTTP ${status})`
107
+ );
108
+ }
109
+
110
+ if (body.status === 'approved') {
111
+ return body.credentials;
112
+ }
113
+
114
+ if (body.status === 'expired') {
115
+ throw new Error(
116
+ 'Device code expired. Please run durjoyai-cli again.'
117
+ );
118
+ }
119
+
120
+ if (body.status === 'error') {
121
+ throw new Error(body.error || 'Server returned an error.');
122
+ }
123
+
124
+ // status === 'pending' → keep polling
125
+ } catch (err) {
126
+ // Network errors during poll — retry until timeout
127
+ if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') continue;
128
+ throw err;
129
+ }
130
+ }
131
+
132
+ throw new Error('Login timed out. Please run durjoyai-cli again.');
133
+ }
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // Main auth export
137
+ // ---------------------------------------------------------------------------
138
+
139
+ /**
140
+ * Full authentication flow using device code:
141
+ * 1. Request device code from backend
142
+ * 2. Open browser to verification URL
143
+ * 3. Poll until approved
144
+ * 4. Return credentials { api_key, base_url, model, ... }
145
+ */
146
+ async function authenticate() {
147
+ // Step 1: Get device code
148
+ const { code, verificationUrl } = await requestDeviceCode();
149
+
150
+ console.log('');
151
+ console.log(` Your code: ${code}`);
152
+ console.log('');
153
+ console.log(' Opening DurjoyAI in your browser...');
154
+ console.log(` If it doesn't open, visit:\n ${verificationUrl}`);
155
+ console.log('');
156
+
157
+ // Step 2: Open browser
158
+ try {
159
+ await openBrowser(verificationUrl);
160
+ } catch {
161
+ // Non-fatal — user can open the URL manually
162
+ }
163
+
164
+ // Step 3: Poll for approval
165
+ console.log(' Waiting for you to approve in browser...');
166
+ const credentials = await pollForApproval(code);
167
+
168
+ return credentials;
169
+ }
170
+
171
+ module.exports = { authenticate, DURJOYAI_API_BASE };
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ const { execSync } = require('child_process');
4
+
5
+ /**
6
+ * Checks whether the Claude CLI is installed and accessible.
7
+ * Returns true if found, false otherwise.
8
+ */
9
+ function isClaudeInstalled() {
10
+ try {
11
+ execSync('claude --version', { stdio: 'ignore' });
12
+ return true;
13
+ } catch {
14
+ return false;
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Prints clear install instructions and exits if Claude CLI is missing.
20
+ */
21
+ function requireClaudeCLI() {
22
+ if (isClaudeInstalled()) return;
23
+
24
+ console.log('');
25
+ console.log(' Claude CLI is not installed on your system.');
26
+ console.log('');
27
+ console.log(' Please install it first by running:');
28
+ console.log('');
29
+ console.log(' npm install -g @anthropic-ai/claude-code');
30
+ console.log('');
31
+ console.log(' Or follow the official guide:');
32
+ console.log(' https://docs.anthropic.com/en/docs/claude-code/getting-started');
33
+ console.log('');
34
+ console.log(' After installing Claude CLI, run this command again.');
35
+ console.log('');
36
+ process.exit(1);
37
+ }
38
+
39
+ module.exports = { isClaudeInstalled, requireClaudeCLI };
package/src/index.js ADDED
@@ -0,0 +1,96 @@
1
+ 'use strict';
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Banner
5
+ // ---------------------------------------------------------------------------
6
+ function printBanner() {
7
+ console.log('');
8
+ console.log(' ╔══════════════════════════════════════╗');
9
+ console.log(' ║ DurjoyAI CLI Setup ║');
10
+ console.log(' ╚══════════════════════════════════════╝');
11
+ console.log('');
12
+ }
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Main flow
16
+ // ---------------------------------------------------------------------------
17
+ async function main() {
18
+ printBanner();
19
+
20
+ const { default: inquirer } = await import('inquirer');
21
+
22
+ // ── Step 1: Check Claude CLI ──────────────────────────────────────────
23
+ const { requireClaudeCLI } = require('./checkClaude');
24
+ requireClaudeCLI();
25
+ console.log(' ✓ Claude CLI detected\n');
26
+
27
+ // ── Step 2: Confirm DurjoyAI user ─────────────────────────────────────
28
+ const { isDurjoyAIUser } = await inquirer.prompt([
29
+ {
30
+ type: 'confirm',
31
+ name: 'isDurjoyAIUser',
32
+ message: 'Are you a DurjoyAI user?',
33
+ default: true,
34
+ },
35
+ ]);
36
+
37
+ if (!isDurjoyAIUser) {
38
+ console.log('');
39
+ console.log(' This tool is for DurjoyAI users.');
40
+ console.log(' Sign up at https://durjoyai.com to get started!');
41
+ console.log('');
42
+ process.exit(0);
43
+ }
44
+
45
+ // ── Step 3: Authenticate via device code ──────────────────────────────
46
+ console.log('');
47
+ console.log(' Step 1/2 — Login');
48
+ console.log(' ─────────────────');
49
+ const { authenticate } = require('./auth');
50
+ const credentials = await authenticate();
51
+
52
+ const name = credentials.user_name || 'there';
53
+ console.log(` ✓ Logged in successfully`);
54
+ console.log(` Welcome, ${name}! (${credentials.plan_type} plan)\n`);
55
+
56
+ // ── Step 4: Save config and env vars ──────────────────────────────────
57
+ console.log(' Step 2/2 — Setting up your environment');
58
+ console.log(' ───────────────────────────────────────');
59
+ const { setupCredentials } = require('./setup');
60
+ const rcFile = setupCredentials({
61
+ base_url: credentials.base_url,
62
+ api_key: credentials.api_key,
63
+ model: credentials.model,
64
+ });
65
+ console.log(` ✓ Environment variables saved to: ${rcFile}`);
66
+
67
+ // ── Step 5: Success message ───────────────────────────────────────────
68
+ const model = credentials.model;
69
+ console.log('');
70
+ console.log(' ╔══════════════════════════════════════════════════════╗');
71
+ console.log(' ║ Setup Complete! ║');
72
+ console.log(' ╚══════════════════════════════════════════════════════╝');
73
+ console.log('');
74
+ console.log(' To start using Claude Code with DurjoyAI:');
75
+ console.log('');
76
+ console.log(' 1. Reload your terminal (or run the command below):');
77
+ console.log(` source ${rcFile}`);
78
+ console.log('');
79
+ console.log(' 2. Then start Claude:');
80
+ console.log(` claude --model ${model}`);
81
+ console.log('');
82
+ console.log(' Happy coding!');
83
+ console.log('');
84
+ }
85
+
86
+ // ---------------------------------------------------------------------------
87
+ // Run with top-level error handling
88
+ // ---------------------------------------------------------------------------
89
+ main().catch((err) => {
90
+ console.error('');
91
+ console.error(' Error:', err.message || err);
92
+ console.error('');
93
+ console.error(' If this keeps happening, contact support at hello@durjoyai.com');
94
+ console.error('');
95
+ process.exit(1);
96
+ });
package/src/setup.js ADDED
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Paths
9
+ // ---------------------------------------------------------------------------
10
+
11
+ const CONFIG_DIR = path.join(os.homedir(), '.durjoyai-cli');
12
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Config persistence
16
+ // ---------------------------------------------------------------------------
17
+
18
+ /**
19
+ * Saves credentials to ~/.durjoyai-cli/config.json
20
+ */
21
+ function saveConfig(credentials) {
22
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
23
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(credentials, null, 2), 'utf8');
24
+ }
25
+
26
+ /**
27
+ * Loads saved credentials, or returns null if none exist.
28
+ */
29
+ function loadConfig() {
30
+ try {
31
+ const raw = fs.readFileSync(CONFIG_FILE, 'utf8');
32
+ return JSON.parse(raw);
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Shell profile detection
40
+ // ---------------------------------------------------------------------------
41
+
42
+ /**
43
+ * Returns the user's shell rc file path.
44
+ */
45
+ function detectShellRcFile() {
46
+ const shell = process.env.SHELL || '';
47
+ if (shell.includes('zsh')) return path.join(os.homedir(), '.zshrc');
48
+ if (shell.includes('fish')) return path.join(os.homedir(), '.config', 'fish', 'config.fish');
49
+ // Default to .bashrc for bash / unknown shells
50
+ return path.join(os.homedir(), '.bashrc');
51
+ }
52
+
53
+ /**
54
+ * Returns true if the shell is fish (needs different export syntax).
55
+ */
56
+ function isFish() {
57
+ return (process.env.SHELL || '').includes('fish');
58
+ }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Env var persistence
62
+ // ---------------------------------------------------------------------------
63
+
64
+ const MARKER_START = '# >>> durjoyai-cli start >>>';
65
+ const MARKER_END = '# <<< durjoyai-cli end <<<';
66
+
67
+ /**
68
+ * Builds the export block to write into the shell rc file.
69
+ */
70
+ function buildExportBlock(credentials) {
71
+ const { base_url, api_key, model } = credentials;
72
+
73
+ if (isFish()) {
74
+ return [
75
+ MARKER_START,
76
+ `set -x ANTHROPIC_BASE_URL "${base_url}"`,
77
+ `set -x ANTHROPIC_API_KEY "${api_key}"`,
78
+ `set -x ANTHROPIC_MODEL "${model}"`,
79
+ MARKER_END,
80
+ ].join('\n');
81
+ }
82
+
83
+ return [
84
+ MARKER_START,
85
+ `export ANTHROPIC_BASE_URL="${base_url}"`,
86
+ `export ANTHROPIC_API_KEY="${api_key}"`,
87
+ `export ANTHROPIC_MODEL="${model}"`,
88
+ MARKER_END,
89
+ ].join('\n');
90
+ }
91
+
92
+ /**
93
+ * Writes (or replaces) the durjoyai-cli export block in the shell rc file.
94
+ * Returns the rc file path that was modified.
95
+ */
96
+ function writeToShellProfile(credentials) {
97
+ const rcFile = detectShellRcFile();
98
+ const block = buildExportBlock(credentials);
99
+
100
+ let existing = '';
101
+ try {
102
+ existing = fs.readFileSync(rcFile, 'utf8');
103
+ } catch {
104
+ // File doesn't exist yet — will be created
105
+ }
106
+
107
+ // Replace the old block if present, otherwise append
108
+ if (existing.includes(MARKER_START)) {
109
+ const re = new RegExp(
110
+ `${escapeRegex(MARKER_START)}[\\s\\S]*?${escapeRegex(MARKER_END)}`,
111
+ 'g'
112
+ );
113
+ existing = existing.replace(re, block);
114
+ } else {
115
+ existing = existing.trimEnd() + '\n\n' + block + '\n';
116
+ }
117
+
118
+ fs.mkdirSync(path.dirname(rcFile), { recursive: true });
119
+ fs.writeFileSync(rcFile, existing, 'utf8');
120
+ return rcFile;
121
+ }
122
+
123
+ function escapeRegex(str) {
124
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
125
+ }
126
+
127
+ // ---------------------------------------------------------------------------
128
+ // Main setup export
129
+ // ---------------------------------------------------------------------------
130
+
131
+ /**
132
+ * Persists credentials:
133
+ * 1. ~/.durjoyai-cli/config.json (for reference / future use)
134
+ * 2. Shell rc file (so env vars are available in every new terminal)
135
+ *
136
+ * Returns the path of the modified rc file.
137
+ */
138
+ function setupCredentials(credentials) {
139
+ saveConfig(credentials);
140
+ const rcFile = writeToShellProfile(credentials);
141
+ return rcFile;
142
+ }
143
+
144
+ module.exports = { setupCredentials, loadConfig, CONFIG_FILE };