@promptcellar/pc 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.
- package/README.md +111 -0
- package/bin/pc.js +69 -0
- package/hooks/prompt-capture.js +76 -0
- package/package.json +43 -0
- package/src/commands/config.js +45 -0
- package/src/commands/login.js +57 -0
- package/src/commands/logout.js +14 -0
- package/src/commands/push.js +56 -0
- package/src/commands/save.js +46 -0
- package/src/commands/setup.js +124 -0
- package/src/commands/status.js +41 -0
- package/src/commands/update.js +45 -0
- package/src/index.js +15 -0
- package/src/lib/api.js +72 -0
- package/src/lib/config.js +70 -0
- package/src/lib/context.js +69 -0
- package/src/lib/websocket.js +78 -0
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# PromptCellar CLI
|
|
2
|
+
|
|
3
|
+
Command-line tool for capturing, managing, and reusing AI prompts with [PromptCellar](https://prompts.weldedanvil.com).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @promptcellar/pc
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Login with your API key
|
|
15
|
+
pc login
|
|
16
|
+
|
|
17
|
+
# Set up auto-capture for Claude Code
|
|
18
|
+
pc setup
|
|
19
|
+
|
|
20
|
+
# Check status
|
|
21
|
+
pc status
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Commands
|
|
25
|
+
|
|
26
|
+
### Authentication
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pc login # Login with your API key
|
|
30
|
+
pc logout # Remove stored credentials
|
|
31
|
+
pc status # Show current status and connection info
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Auto-Capture Setup
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pc setup # Configure auto-capture for CLI tools
|
|
38
|
+
pc unsetup # Remove auto-capture hooks
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Currently supports:
|
|
42
|
+
- Claude Code (via Stop hooks)
|
|
43
|
+
|
|
44
|
+
Coming soon:
|
|
45
|
+
- Cursor
|
|
46
|
+
- Windsurf
|
|
47
|
+
|
|
48
|
+
### Manual Capture
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Save a prompt manually
|
|
52
|
+
pc save -m "Your prompt text here"
|
|
53
|
+
|
|
54
|
+
# Open editor to write prompt
|
|
55
|
+
pc save
|
|
56
|
+
|
|
57
|
+
# Specify tool and model
|
|
58
|
+
pc save -m "prompt" -t aider -M gpt-4
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Push Prompts
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Fetch and display a prompt by slug
|
|
65
|
+
pc push my-project/useful-prompt
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Configuration
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# View/change capture level
|
|
72
|
+
pc config
|
|
73
|
+
|
|
74
|
+
# Set capture level directly
|
|
75
|
+
pc config --level minimal # Tool + timestamp only
|
|
76
|
+
pc config --level standard # + working directory, git repo/branch
|
|
77
|
+
pc config --level rich # + session ID, git commit, remote URL
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Update
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pc update # Update to latest version
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Capture Levels
|
|
87
|
+
|
|
88
|
+
Control how much context is captured with your prompts:
|
|
89
|
+
|
|
90
|
+
| Level | Captured Data |
|
|
91
|
+
|-------|--------------|
|
|
92
|
+
| `minimal` | Tool, timestamp |
|
|
93
|
+
| `standard` | + Working directory, git repo, git branch |
|
|
94
|
+
| `rich` | + Session ID, git commit hash, remote URL |
|
|
95
|
+
|
|
96
|
+
## Environment
|
|
97
|
+
|
|
98
|
+
Configuration is stored in:
|
|
99
|
+
- macOS: `~/Library/Preferences/promptcellar-nodejs/`
|
|
100
|
+
- Linux: `~/.config/promptcellar-nodejs/`
|
|
101
|
+
- Windows: `%APPDATA%/promptcellar-nodejs/`
|
|
102
|
+
|
|
103
|
+
## API
|
|
104
|
+
|
|
105
|
+
The CLI communicates with your PromptCellar instance. Get your API key from:
|
|
106
|
+
|
|
107
|
+
Settings > API Keys in your PromptCellar dashboard.
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT
|
package/bin/pc.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { program } from 'commander';
|
|
4
|
+
import { login } from '../src/commands/login.js';
|
|
5
|
+
import { logout } from '../src/commands/logout.js';
|
|
6
|
+
import { status } from '../src/commands/status.js';
|
|
7
|
+
import { setup, unsetup } from '../src/commands/setup.js';
|
|
8
|
+
import { save } from '../src/commands/save.js';
|
|
9
|
+
import { push } from '../src/commands/push.js';
|
|
10
|
+
import { config } from '../src/commands/config.js';
|
|
11
|
+
import { update } from '../src/commands/update.js';
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.name('pc')
|
|
15
|
+
.description('PromptCellar CLI - sync prompts between your terminal and the cloud')
|
|
16
|
+
.version('0.1.0');
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.command('login')
|
|
20
|
+
.description('Authenticate with PromptCellar')
|
|
21
|
+
.action(login);
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.command('logout')
|
|
25
|
+
.description('Clear stored credentials')
|
|
26
|
+
.action(logout);
|
|
27
|
+
|
|
28
|
+
program
|
|
29
|
+
.command('status')
|
|
30
|
+
.description('Show connection status and active sessions')
|
|
31
|
+
.action(status);
|
|
32
|
+
|
|
33
|
+
program
|
|
34
|
+
.command('setup')
|
|
35
|
+
.description('Auto-detect installed tools and configure hooks/wrappers')
|
|
36
|
+
.option('--force', 'Overwrite existing hooks/wrappers')
|
|
37
|
+
.action(setup);
|
|
38
|
+
|
|
39
|
+
program
|
|
40
|
+
.command('save')
|
|
41
|
+
.description('Manually save a prompt to current project')
|
|
42
|
+
.option('-m, --message <prompt>', 'Prompt content (opens editor if not provided)')
|
|
43
|
+
.option('-t, --tool <tool>', 'Tool name (default: manual)')
|
|
44
|
+
.option('-M, --model <model>', 'Model used')
|
|
45
|
+
.action(save);
|
|
46
|
+
|
|
47
|
+
program
|
|
48
|
+
.command('unsetup')
|
|
49
|
+
.description('Remove auto-capture hooks')
|
|
50
|
+
.action(unsetup);
|
|
51
|
+
|
|
52
|
+
program
|
|
53
|
+
.command('push <slug>')
|
|
54
|
+
.description('Push a prompt to the active CLI session')
|
|
55
|
+
.option('-t, --tool <tool>', 'Target tool (default: claude)')
|
|
56
|
+
.action(push);
|
|
57
|
+
|
|
58
|
+
program
|
|
59
|
+
.command('config')
|
|
60
|
+
.description('Adjust privacy/capture settings')
|
|
61
|
+
.option('-l, --level <level>', 'Set capture level: minimal, standard, or rich')
|
|
62
|
+
.action(config);
|
|
63
|
+
|
|
64
|
+
program
|
|
65
|
+
.command('update')
|
|
66
|
+
.description('Self-update to latest version')
|
|
67
|
+
.action(update);
|
|
68
|
+
|
|
69
|
+
program.parse();
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Claude Code Stop hook for capturing prompts to PromptCellar.
|
|
5
|
+
*
|
|
6
|
+
* This script is called by Claude Code when a session ends.
|
|
7
|
+
* It parses the transcript file and extracts user prompts to capture.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, existsSync } from 'fs';
|
|
11
|
+
import { capturePrompt } from '../src/lib/api.js';
|
|
12
|
+
import { getFullContext } from '../src/lib/context.js';
|
|
13
|
+
import { isLoggedIn } from '../src/lib/config.js';
|
|
14
|
+
|
|
15
|
+
async function main() {
|
|
16
|
+
const transcriptPath = process.argv[2];
|
|
17
|
+
|
|
18
|
+
if (!transcriptPath) {
|
|
19
|
+
console.error('Usage: pc-capture <transcript-file>');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!existsSync(transcriptPath)) {
|
|
24
|
+
console.error('Transcript file not found:', transcriptPath);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!isLoggedIn()) {
|
|
29
|
+
// Silently exit if not logged in
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const content = readFileSync(transcriptPath, 'utf8');
|
|
35
|
+
const lines = content.split('\n').filter(line => line.trim());
|
|
36
|
+
|
|
37
|
+
// Parse JSONL format
|
|
38
|
+
const messages = [];
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
try {
|
|
41
|
+
const entry = JSON.parse(line);
|
|
42
|
+
if (entry.type === 'human' && entry.message?.content) {
|
|
43
|
+
messages.push({
|
|
44
|
+
content: entry.message.content,
|
|
45
|
+
timestamp: entry.timestamp
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// Skip invalid JSON lines
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (messages.length === 0) {
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Capture the first user message (initial prompt)
|
|
58
|
+
const initialPrompt = messages[0];
|
|
59
|
+
const context = getFullContext('claude-code');
|
|
60
|
+
|
|
61
|
+
await capturePrompt({
|
|
62
|
+
content: initialPrompt.content,
|
|
63
|
+
...context,
|
|
64
|
+
captured_at: initialPrompt.timestamp
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Optionally capture all prompts if configured
|
|
68
|
+
// For now, just capture the initial prompt
|
|
69
|
+
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('Error capturing prompt:', error.message);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@promptcellar/pc",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for PromptCellar - sync prompts between your terminal and the cloud",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"pc": "bin/pc.js",
|
|
9
|
+
"pc-capture": "hooks/prompt-capture.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"promptcellar",
|
|
16
|
+
"prompts",
|
|
17
|
+
"cli",
|
|
18
|
+
"ai",
|
|
19
|
+
"llm",
|
|
20
|
+
"claude",
|
|
21
|
+
"codex",
|
|
22
|
+
"gemini"
|
|
23
|
+
],
|
|
24
|
+
"author": "Welded Anvil Technologies LLC",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/WeldedAnvil/pc-cli.git"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://prompts.weldedanvil.com",
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18.0.0"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"commander": "^12.0.0",
|
|
36
|
+
"conf": "^12.0.0",
|
|
37
|
+
"chalk": "^5.3.0",
|
|
38
|
+
"ora": "^8.0.0",
|
|
39
|
+
"inquirer": "^9.2.0",
|
|
40
|
+
"socket.io-client": "^4.6.0",
|
|
41
|
+
"node-fetch": "^3.3.0"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { getCaptureLevel, setCaptureLevel } from '../lib/config.js';
|
|
4
|
+
|
|
5
|
+
const CAPTURE_LEVELS = {
|
|
6
|
+
minimal: 'Just tool and timestamp',
|
|
7
|
+
standard: 'Tool, timestamp, working directory, git repo/branch',
|
|
8
|
+
rich: 'Everything including session ID, git commit, remote URL'
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export async function config(options) {
|
|
12
|
+
if (options.level) {
|
|
13
|
+
try {
|
|
14
|
+
setCaptureLevel(options.level);
|
|
15
|
+
console.log(chalk.green(`Capture level set to: ${options.level}`));
|
|
16
|
+
console.log(chalk.dim(CAPTURE_LEVELS[options.level]));
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.log(chalk.red(error.message));
|
|
19
|
+
}
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const currentLevel = getCaptureLevel();
|
|
24
|
+
|
|
25
|
+
console.log(chalk.bold('\nCapture Level Configuration\n'));
|
|
26
|
+
console.log(`Current level: ${chalk.cyan(currentLevel)}\n`);
|
|
27
|
+
|
|
28
|
+
const choices = Object.entries(CAPTURE_LEVELS).map(([level, desc]) => ({
|
|
29
|
+
name: `${level} - ${desc}`,
|
|
30
|
+
value: level
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
const { level } = await inquirer.prompt([{
|
|
34
|
+
type: 'list',
|
|
35
|
+
name: 'level',
|
|
36
|
+
message: 'Select capture level:',
|
|
37
|
+
choices,
|
|
38
|
+
default: currentLevel
|
|
39
|
+
}]);
|
|
40
|
+
|
|
41
|
+
setCaptureLevel(level);
|
|
42
|
+
console.log(chalk.green(`\nCapture level set to: ${level}`));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default config;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { setApiKey, setApiUrl, isLoggedIn } from '../lib/config.js';
|
|
5
|
+
import { testConnection } from '../lib/api.js';
|
|
6
|
+
|
|
7
|
+
export async function login() {
|
|
8
|
+
if (isLoggedIn()) {
|
|
9
|
+
const { overwrite } = await inquirer.prompt([{
|
|
10
|
+
type: 'confirm',
|
|
11
|
+
name: 'overwrite',
|
|
12
|
+
message: 'Already logged in. Replace existing credentials?',
|
|
13
|
+
default: false
|
|
14
|
+
}]);
|
|
15
|
+
|
|
16
|
+
if (!overwrite) {
|
|
17
|
+
console.log('Login cancelled.');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const answers = await inquirer.prompt([
|
|
23
|
+
{
|
|
24
|
+
type: 'input',
|
|
25
|
+
name: 'apiKey',
|
|
26
|
+
message: 'Enter your PromptCellar API key:',
|
|
27
|
+
validate: (input) => {
|
|
28
|
+
if (!input.trim()) return 'API key is required';
|
|
29
|
+
if (!input.startsWith('pk_')) return 'Invalid API key format (should start with pk_)';
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
type: 'input',
|
|
35
|
+
name: 'apiUrl',
|
|
36
|
+
message: 'API URL (press enter for default):',
|
|
37
|
+
default: 'https://prompts.weldedanvil.com'
|
|
38
|
+
}
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const spinner = ora('Testing connection...').start();
|
|
42
|
+
|
|
43
|
+
setApiKey(answers.apiKey.trim());
|
|
44
|
+
setApiUrl(answers.apiUrl.trim());
|
|
45
|
+
|
|
46
|
+
const result = await testConnection();
|
|
47
|
+
|
|
48
|
+
if (result.success) {
|
|
49
|
+
spinner.succeed(chalk.green('Logged in successfully!'));
|
|
50
|
+
console.log('\nRun ' + chalk.cyan('pc setup') + ' to configure auto-capture for your CLI tools.');
|
|
51
|
+
} else {
|
|
52
|
+
spinner.fail(chalk.red('Connection failed: ' + result.error));
|
|
53
|
+
console.log('Check your API key and try again.');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default login;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { clearApiKey, isLoggedIn } from '../lib/config.js';
|
|
3
|
+
|
|
4
|
+
export async function logout() {
|
|
5
|
+
if (!isLoggedIn()) {
|
|
6
|
+
console.log('Not logged in.');
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
clearApiKey();
|
|
11
|
+
console.log(chalk.green('Logged out successfully.'));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default logout;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { getPrompt } from '../lib/api.js';
|
|
4
|
+
import { connect, disconnect } from '../lib/websocket.js';
|
|
5
|
+
import { isLoggedIn } from '../lib/config.js';
|
|
6
|
+
|
|
7
|
+
export async function push(slug, options) {
|
|
8
|
+
if (!isLoggedIn()) {
|
|
9
|
+
console.log(chalk.red('Not logged in.') + ' Run: pc login');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const spinner = ora('Fetching prompt...').start();
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const prompt = await getPrompt(slug);
|
|
17
|
+
spinner.text = 'Connecting to session...';
|
|
18
|
+
|
|
19
|
+
const socket = connect(options.tool || 'claude');
|
|
20
|
+
|
|
21
|
+
// Wait for connection
|
|
22
|
+
await new Promise((resolve, reject) => {
|
|
23
|
+
const timeout = setTimeout(() => {
|
|
24
|
+
reject(new Error('Connection timeout'));
|
|
25
|
+
}, 10000);
|
|
26
|
+
|
|
27
|
+
socket.on('registered', () => {
|
|
28
|
+
clearTimeout(timeout);
|
|
29
|
+
resolve();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
socket.on('error', (err) => {
|
|
33
|
+
clearTimeout(timeout);
|
|
34
|
+
reject(new Error(err.message));
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
spinner.text = 'Pushing prompt...';
|
|
39
|
+
|
|
40
|
+
// Push the prompt to ourselves (for now, just copy to clipboard simulation)
|
|
41
|
+
console.log('\n' + chalk.cyan('Prompt content:'));
|
|
42
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
43
|
+
console.log(prompt.content);
|
|
44
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
45
|
+
|
|
46
|
+
spinner.succeed(chalk.green('Prompt fetched!'));
|
|
47
|
+
console.log('\nCopy the prompt above to use it in your session.');
|
|
48
|
+
|
|
49
|
+
disconnect();
|
|
50
|
+
} catch (error) {
|
|
51
|
+
spinner.fail(chalk.red('Failed: ' + error.message));
|
|
52
|
+
disconnect();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default push;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { capturePrompt } from '../lib/api.js';
|
|
5
|
+
import { getFullContext } from '../lib/context.js';
|
|
6
|
+
import { isLoggedIn } from '../lib/config.js';
|
|
7
|
+
|
|
8
|
+
export async function save(options) {
|
|
9
|
+
if (!isLoggedIn()) {
|
|
10
|
+
console.log(chalk.red('Not logged in.') + ' Run: pc login');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let content = options.message;
|
|
15
|
+
|
|
16
|
+
if (!content) {
|
|
17
|
+
const { promptContent } = await inquirer.prompt([{
|
|
18
|
+
type: 'editor',
|
|
19
|
+
name: 'promptContent',
|
|
20
|
+
message: 'Enter your prompt (opens editor):',
|
|
21
|
+
validate: (input) => {
|
|
22
|
+
if (!input.trim()) return 'Prompt content is required';
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}]);
|
|
26
|
+
content = promptContent;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const context = getFullContext(options.tool || 'manual', options.model);
|
|
30
|
+
|
|
31
|
+
const spinner = ora('Saving prompt...').start();
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const result = await capturePrompt({
|
|
35
|
+
content: content.trim(),
|
|
36
|
+
...context
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
spinner.succeed(chalk.green('Prompt saved!'));
|
|
40
|
+
console.log(` ID: ${chalk.dim(result.id)}`);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
spinner.fail(chalk.red('Failed to save: ' + error.message));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default save;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
4
|
+
import { join, dirname } from 'path';
|
|
5
|
+
import { homedir } from 'os';
|
|
6
|
+
|
|
7
|
+
const CLAUDE_HOOKS_PATH = join(homedir(), '.claude', 'hooks.json');
|
|
8
|
+
const HOOK_SCRIPT_NAME = 'pc-capture';
|
|
9
|
+
|
|
10
|
+
function getHooksConfig() {
|
|
11
|
+
if (existsSync(CLAUDE_HOOKS_PATH)) {
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(readFileSync(CLAUDE_HOOKS_PATH, 'utf8'));
|
|
14
|
+
} catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function saveHooksConfig(config) {
|
|
22
|
+
const dir = dirname(CLAUDE_HOOKS_PATH);
|
|
23
|
+
if (!existsSync(dir)) {
|
|
24
|
+
mkdirSync(dir, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
writeFileSync(CLAUDE_HOOKS_PATH, JSON.stringify(config, null, 2));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isHookInstalled(config) {
|
|
30
|
+
const stopHooks = config.Stop || [];
|
|
31
|
+
return stopHooks.some(hook =>
|
|
32
|
+
hook.command && hook.command.includes(HOOK_SCRIPT_NAME)
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function setup() {
|
|
37
|
+
console.log(chalk.bold('\nPromptCellar CLI Setup\n'));
|
|
38
|
+
|
|
39
|
+
const tools = [
|
|
40
|
+
{ name: 'Claude Code', value: 'claude', checked: true },
|
|
41
|
+
{ name: 'Cursor (coming soon)', value: 'cursor', disabled: true },
|
|
42
|
+
{ name: 'Windsurf (coming soon)', value: 'windsurf', disabled: true }
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const { selectedTools } = await inquirer.prompt([{
|
|
46
|
+
type: 'checkbox',
|
|
47
|
+
name: 'selectedTools',
|
|
48
|
+
message: 'Which tools do you want to capture prompts from?',
|
|
49
|
+
choices: tools
|
|
50
|
+
}]);
|
|
51
|
+
|
|
52
|
+
if (selectedTools.length === 0) {
|
|
53
|
+
console.log(chalk.yellow('No tools selected. Setup cancelled.'));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const tool of selectedTools) {
|
|
58
|
+
if (tool === 'claude') {
|
|
59
|
+
await setupClaudeCode();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log(chalk.green('\nSetup complete!'));
|
|
64
|
+
console.log('Your prompts will be automatically captured to PromptCellar.\n');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function setupClaudeCode() {
|
|
68
|
+
console.log(chalk.cyan('\nConfiguring Claude Code...'));
|
|
69
|
+
|
|
70
|
+
const config = getHooksConfig();
|
|
71
|
+
|
|
72
|
+
if (isHookInstalled(config)) {
|
|
73
|
+
console.log(chalk.yellow(' Hook already installed.'));
|
|
74
|
+
|
|
75
|
+
const { reinstall } = await inquirer.prompt([{
|
|
76
|
+
type: 'confirm',
|
|
77
|
+
name: 'reinstall',
|
|
78
|
+
message: 'Reinstall the hook?',
|
|
79
|
+
default: false
|
|
80
|
+
}]);
|
|
81
|
+
|
|
82
|
+
if (!reinstall) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Remove existing hook
|
|
87
|
+
config.Stop = (config.Stop || []).filter(hook =>
|
|
88
|
+
!hook.command || !hook.command.includes(HOOK_SCRIPT_NAME)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Add the hook
|
|
93
|
+
if (!config.Stop) {
|
|
94
|
+
config.Stop = [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
config.Stop.push({
|
|
98
|
+
command: `pc-capture "$CLAUDE_TRANSCRIPT_FILE"`,
|
|
99
|
+
description: 'Capture prompts to PromptCellar'
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
saveHooksConfig(config);
|
|
103
|
+
console.log(chalk.green(' Hook installed successfully.'));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function unsetup() {
|
|
107
|
+
console.log(chalk.bold('\nRemoving PromptCellar hooks...\n'));
|
|
108
|
+
|
|
109
|
+
const config = getHooksConfig();
|
|
110
|
+
|
|
111
|
+
if (!isHookInstalled(config)) {
|
|
112
|
+
console.log(chalk.yellow('No hooks installed.'));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
config.Stop = (config.Stop || []).filter(hook =>
|
|
117
|
+
!hook.command || !hook.command.includes(HOOK_SCRIPT_NAME)
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
saveHooksConfig(config);
|
|
121
|
+
console.log(chalk.green('Hooks removed successfully.'));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export default setup;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { isLoggedIn, getApiUrl, getCaptureLevel, getSessionId } from '../lib/config.js';
|
|
3
|
+
import { testConnection } from '../lib/api.js';
|
|
4
|
+
import { getGitContext } from '../lib/context.js';
|
|
5
|
+
|
|
6
|
+
export async function status() {
|
|
7
|
+
console.log(chalk.bold('\nPromptCellar CLI Status\n'));
|
|
8
|
+
|
|
9
|
+
// Auth status
|
|
10
|
+
if (isLoggedIn()) {
|
|
11
|
+
const result = await testConnection();
|
|
12
|
+
if (result.success) {
|
|
13
|
+
console.log(chalk.green(' Logged in') + ` to ${getApiUrl()}`);
|
|
14
|
+
} else {
|
|
15
|
+
console.log(chalk.yellow(' Logged in') + ` but connection failed: ${result.error}`);
|
|
16
|
+
}
|
|
17
|
+
} else {
|
|
18
|
+
console.log(chalk.red(' Not logged in') + ' - Run: pc login');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Capture level
|
|
22
|
+
console.log(` Capture level: ${chalk.cyan(getCaptureLevel())}`);
|
|
23
|
+
|
|
24
|
+
// Session ID
|
|
25
|
+
console.log(` Session ID: ${chalk.dim(getSessionId().slice(0, 8))}...`);
|
|
26
|
+
|
|
27
|
+
// Git context
|
|
28
|
+
const git = getGitContext();
|
|
29
|
+
if (git.git_repo) {
|
|
30
|
+
console.log(`\n Git repo: ${chalk.cyan(git.git_repo)}`);
|
|
31
|
+
if (git.git_branch) {
|
|
32
|
+
console.log(` Branch: ${chalk.cyan(git.git_branch)}`);
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
console.log(chalk.dim('\n Not in a git repository'));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log('');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default status;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { execFileSync } from 'child_process';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
export async function update() {
|
|
6
|
+
const spinner = ora('Checking for updates...').start();
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
// Check current version
|
|
10
|
+
const currentVersion = JSON.parse(
|
|
11
|
+
execFileSync('npm', ['pkg', 'get', 'version'], { encoding: 'utf8' })
|
|
12
|
+
).replace(/"/g, '');
|
|
13
|
+
|
|
14
|
+
// Check latest version from npm
|
|
15
|
+
let latestVersion;
|
|
16
|
+
try {
|
|
17
|
+
const output = execFileSync('npm', ['view', '@promptcellar/pc', 'version'], {
|
|
18
|
+
encoding: 'utf8',
|
|
19
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
20
|
+
}).trim();
|
|
21
|
+
latestVersion = output;
|
|
22
|
+
} catch {
|
|
23
|
+
spinner.fail(chalk.red('Failed to check for updates.'));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (currentVersion === latestVersion) {
|
|
28
|
+
spinner.succeed(chalk.green(`Already on latest version (${currentVersion})`));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
spinner.text = `Updating from ${currentVersion} to ${latestVersion}...`;
|
|
33
|
+
|
|
34
|
+
execFileSync('npm', ['install', '-g', '@promptcellar/pc@latest'], {
|
|
35
|
+
stdio: 'inherit'
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
spinner.succeed(chalk.green(`Updated to version ${latestVersion}`));
|
|
39
|
+
} catch (error) {
|
|
40
|
+
spinner.fail(chalk.red('Update failed: ' + error.message));
|
|
41
|
+
console.log('Try running: npm install -g @promptcellar/pc@latest');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default update;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// PromptCellar CLI - Main exports
|
|
2
|
+
|
|
3
|
+
export { login } from './commands/login.js';
|
|
4
|
+
export { logout } from './commands/logout.js';
|
|
5
|
+
export { status } from './commands/status.js';
|
|
6
|
+
export { setup, unsetup } from './commands/setup.js';
|
|
7
|
+
export { save } from './commands/save.js';
|
|
8
|
+
export { push } from './commands/push.js';
|
|
9
|
+
export { config } from './commands/config.js';
|
|
10
|
+
export { update } from './commands/update.js';
|
|
11
|
+
|
|
12
|
+
export * from './lib/config.js';
|
|
13
|
+
export * from './lib/api.js';
|
|
14
|
+
export * from './lib/context.js';
|
|
15
|
+
export * from './lib/websocket.js';
|
package/src/lib/api.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import { getApiKey, getApiUrl } from './config.js';
|
|
3
|
+
|
|
4
|
+
async function request(endpoint, options = {}) {
|
|
5
|
+
const apiKey = getApiKey();
|
|
6
|
+
const apiUrl = getApiUrl();
|
|
7
|
+
|
|
8
|
+
if (!apiKey) {
|
|
9
|
+
throw new Error('Not logged in. Run: pc login');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const url = `${apiUrl}${endpoint}`;
|
|
13
|
+
const headers = {
|
|
14
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
...options.headers
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const response = await fetch(url, {
|
|
20
|
+
...options,
|
|
21
|
+
headers
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
const data = await response.json().catch(() => ({}));
|
|
26
|
+
throw new Error(data.error || `HTTP ${response.status}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return response.json();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function testConnection() {
|
|
33
|
+
const apiKey = getApiKey();
|
|
34
|
+
const apiUrl = getApiUrl();
|
|
35
|
+
|
|
36
|
+
if (!apiKey) {
|
|
37
|
+
return { success: false, error: 'Not logged in' };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const response = await fetch(`${apiUrl}/health`);
|
|
42
|
+
if (response.ok) {
|
|
43
|
+
return { success: true };
|
|
44
|
+
}
|
|
45
|
+
return { success: false, error: 'Server not responding' };
|
|
46
|
+
} catch (error) {
|
|
47
|
+
return { success: false, error: error.message };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function capturePrompt(data) {
|
|
52
|
+
return request('/api/v1/capture', {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
body: JSON.stringify(data)
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function listCaptured(options = {}) {
|
|
59
|
+
const params = new URLSearchParams();
|
|
60
|
+
if (options.page) params.set('page', options.page);
|
|
61
|
+
if (options.tool) params.set('tool', options.tool);
|
|
62
|
+
if (options.starred) params.set('starred', 'true');
|
|
63
|
+
|
|
64
|
+
const query = params.toString();
|
|
65
|
+
return request(`/api/v1/captured${query ? '?' + query : ''}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function getPrompt(slug) {
|
|
69
|
+
return request(`/api/v1/prompts/${slug}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export default { testConnection, capturePrompt, listCaptured, getPrompt };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
|
|
3
|
+
const config = new Conf({
|
|
4
|
+
projectName: 'promptcellar',
|
|
5
|
+
schema: {
|
|
6
|
+
apiKey: {
|
|
7
|
+
type: 'string',
|
|
8
|
+
default: ''
|
|
9
|
+
},
|
|
10
|
+
apiUrl: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
default: 'https://prompts.weldedanvil.com'
|
|
13
|
+
},
|
|
14
|
+
captureLevel: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
enum: ['minimal', 'standard', 'rich'],
|
|
17
|
+
default: 'rich'
|
|
18
|
+
},
|
|
19
|
+
sessionId: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
default: ''
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export function getApiKey() {
|
|
27
|
+
return config.get('apiKey');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function setApiKey(key) {
|
|
31
|
+
config.set('apiKey', key);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function clearApiKey() {
|
|
35
|
+
config.delete('apiKey');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getApiUrl() {
|
|
39
|
+
return config.get('apiUrl');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function setApiUrl(url) {
|
|
43
|
+
config.set('apiUrl', url);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getCaptureLevel() {
|
|
47
|
+
return config.get('captureLevel');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function setCaptureLevel(level) {
|
|
51
|
+
if (!['minimal', 'standard', 'rich'].includes(level)) {
|
|
52
|
+
throw new Error('Invalid capture level. Must be: minimal, standard, or rich');
|
|
53
|
+
}
|
|
54
|
+
config.set('captureLevel', level);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getSessionId() {
|
|
58
|
+
let sessionId = config.get('sessionId');
|
|
59
|
+
if (!sessionId) {
|
|
60
|
+
sessionId = crypto.randomUUID();
|
|
61
|
+
config.set('sessionId', sessionId);
|
|
62
|
+
}
|
|
63
|
+
return sessionId;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function isLoggedIn() {
|
|
67
|
+
return !!config.get('apiKey');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default config;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { execFileSync } from 'child_process';
|
|
2
|
+
import { getCaptureLevel, getSessionId } from './config.js';
|
|
3
|
+
|
|
4
|
+
function execGit(...args) {
|
|
5
|
+
try {
|
|
6
|
+
return execFileSync('git', args, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
7
|
+
} catch {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getGitContext() {
|
|
13
|
+
const level = getCaptureLevel();
|
|
14
|
+
|
|
15
|
+
// Minimal: no git context
|
|
16
|
+
if (level === 'minimal') {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const context = {};
|
|
21
|
+
|
|
22
|
+
// Standard: repo name and branch
|
|
23
|
+
const topLevel = execGit('rev-parse', '--show-toplevel');
|
|
24
|
+
if (topLevel) {
|
|
25
|
+
context.git_repo = topLevel.split('/').pop();
|
|
26
|
+
context.git_branch = execGit('rev-parse', '--abbrev-ref', 'HEAD');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Rich: add remote URL and commit hash
|
|
30
|
+
if (level === 'rich' && topLevel) {
|
|
31
|
+
context.git_remote_url = execGit('remote', 'get-url', 'origin');
|
|
32
|
+
context.git_commit = execGit('rev-parse', 'HEAD');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return context;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getFullContext(tool, model) {
|
|
39
|
+
const level = getCaptureLevel();
|
|
40
|
+
const sessionId = getSessionId();
|
|
41
|
+
|
|
42
|
+
const context = {
|
|
43
|
+
tool,
|
|
44
|
+
captured_at: new Date().toISOString()
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Minimal: just tool and timestamp
|
|
48
|
+
if (level === 'minimal') {
|
|
49
|
+
return context;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Standard: add working directory and model
|
|
53
|
+
context.working_directory = process.cwd();
|
|
54
|
+
if (model) {
|
|
55
|
+
context.model = model;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Add git context
|
|
59
|
+
Object.assign(context, getGitContext());
|
|
60
|
+
|
|
61
|
+
// Rich: add session ID
|
|
62
|
+
if (level === 'rich') {
|
|
63
|
+
context.session_id = sessionId;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return context;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default { getGitContext, getFullContext };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { io } from 'socket.io-client';
|
|
2
|
+
import { getApiKey, getApiUrl, getSessionId } from './config.js';
|
|
3
|
+
import { getFullContext } from './context.js';
|
|
4
|
+
|
|
5
|
+
let socket = null;
|
|
6
|
+
let reconnectTimer = null;
|
|
7
|
+
|
|
8
|
+
export function connect(tool, onPromptReceived) {
|
|
9
|
+
const apiKey = getApiKey();
|
|
10
|
+
const apiUrl = getApiUrl();
|
|
11
|
+
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
throw new Error('Not logged in. Run: pc login');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (socket?.connected) {
|
|
17
|
+
return socket;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
socket = io(apiUrl, {
|
|
21
|
+
transports: ['websocket'],
|
|
22
|
+
reconnection: true,
|
|
23
|
+
reconnectionDelay: 1000,
|
|
24
|
+
reconnectionDelayMax: 5000
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
socket.on('connect', () => {
|
|
28
|
+
const context = getFullContext(tool);
|
|
29
|
+
socket.emit('register_cli', {
|
|
30
|
+
api_key: apiKey,
|
|
31
|
+
session_id: getSessionId(),
|
|
32
|
+
tool,
|
|
33
|
+
working_directory: context.working_directory,
|
|
34
|
+
git_repo: context.git_repo,
|
|
35
|
+
git_branch: context.git_branch
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
socket.on('registered', (data) => {
|
|
40
|
+
console.log(`Connected to PromptCellar (session: ${data.session_id})`);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
socket.on('prompt_pushed', (data) => {
|
|
44
|
+
if (onPromptReceived) {
|
|
45
|
+
onPromptReceived(data);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
socket.on('error', (data) => {
|
|
50
|
+
console.error('WebSocket error:', data.message);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
socket.on('disconnect', (reason) => {
|
|
54
|
+
if (reason === 'io server disconnect') {
|
|
55
|
+
// Server disconnected, try to reconnect
|
|
56
|
+
socket.connect();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return socket;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function disconnect() {
|
|
64
|
+
if (socket) {
|
|
65
|
+
socket.disconnect();
|
|
66
|
+
socket = null;
|
|
67
|
+
}
|
|
68
|
+
if (reconnectTimer) {
|
|
69
|
+
clearTimeout(reconnectTimer);
|
|
70
|
+
reconnectTimer = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function isConnected() {
|
|
75
|
+
return socket?.connected || false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default { connect, disconnect, isConnected };
|