@promptcellar/pc 0.5.3 → 0.5.5

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,23 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { sanitizeGitRemote } from '../src/lib/context.js';
4
+
5
+ describe('sanitizeGitRemote', () => {
6
+ it('strips credentials and query/fragment', () => {
7
+ const input = 'https://token:secret@github.com/org/repo.git?foo=bar#frag';
8
+ const output = sanitizeGitRemote(input);
9
+ assert.equal(output, 'https://github.com/org/repo.git');
10
+ });
11
+
12
+ it('removes userinfo from https URL', () => {
13
+ const input = 'https://token@github.com/org/repo.git';
14
+ const output = sanitizeGitRemote(input);
15
+ assert.equal(output, 'https://github.com/org/repo.git');
16
+ });
17
+
18
+ it('leaves scp-style remotes intact', () => {
19
+ const input = 'git@github.com:org/repo.git';
20
+ const output = sanitizeGitRemote(input);
21
+ assert.equal(output, input);
22
+ });
23
+ });
@@ -118,4 +118,32 @@ describe('device login helpers', () => {
118
118
  assert.equal(exchangeCalled, false);
119
119
  });
120
120
  });
121
+
122
+ describe('persistLoginState', () => {
123
+ it('does not persist when validation fails', () => {
124
+ const { persistLoginState } = login;
125
+ assert.equal(typeof persistLoginState, 'function', 'persistLoginState should be exported');
126
+
127
+ const calls = [];
128
+ const normalizeFn = () => {
129
+ throw new Error('invalid url');
130
+ };
131
+
132
+ assert.throws(
133
+ () => persistLoginState({
134
+ apiUrl: 'http://bad',
135
+ accountUrl: 'http://bad',
136
+ apiKey: 'api-key',
137
+ normalizeFn,
138
+ setApiUrlFn: () => calls.push('apiUrl'),
139
+ setAccountUrlFn: () => calls.push('accountUrl'),
140
+ setApiKeyFn: () => calls.push('apiKey'),
141
+ setVaultAvailableFn: () => calls.push('vaultAvailable'),
142
+ }),
143
+ /invalid url/i,
144
+ );
145
+
146
+ assert.deepEqual(calls, []);
147
+ });
148
+ });
121
149
  });
@@ -1,6 +1,14 @@
1
- import { describe, it } from 'node:test';
1
+ import { describe, it, mock } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
+ import keytar from 'keytar';
3
4
  import * as keychain from '../src/lib/keychain.js';
5
+ import { loadVaultKey, storeVaultKey } from '../src/lib/keychain.js';
6
+
7
+ const ORIGINAL_ENV = { ...process.env };
8
+
9
+ function resetEnv() {
10
+ process.env = { ...ORIGINAL_ENV };
11
+ }
4
12
 
5
13
  describe('requireVaultKey', () => {
6
14
  it('returns key when available', async () => {
@@ -38,3 +46,28 @@ describe('requireVaultKey', () => {
38
46
  assert.equal(key, null);
39
47
  });
40
48
  });
49
+
50
+ describe('vault key env handling', () => {
51
+ it('prefers PC_VAULT_KEY when set', async () => {
52
+ resetEnv();
53
+ process.env.PC_VAULT_KEY = 'env-key';
54
+ mock.method(keytar, 'getPassword', async () => {
55
+ throw new Error('should not call keytar');
56
+ });
57
+
58
+ const key = await loadVaultKey();
59
+ assert.equal(key, 'env-key');
60
+ });
61
+
62
+ it('throws when keytar fails and fallback disabled', async () => {
63
+ resetEnv();
64
+ mock.method(keytar, 'setPassword', async () => {
65
+ throw new Error('keytar locked');
66
+ });
67
+
68
+ await assert.rejects(
69
+ () => storeVaultKey('vault-key'),
70
+ /keytar locked/i,
71
+ );
72
+ });
73
+ });
package/README.md DELETED
@@ -1,114 +0,0 @@
1
- # Prompt Cellar CLI
2
-
3
- Command-line tool for capturing, managing, and reusing AI prompts with [Prompt Cellar](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 via browser authorization
15
- pc login
16
-
17
- # Set up auto-capture for your AI CLI tools
18
- pc setup
19
-
20
- # Check status
21
- pc status
22
- ```
23
-
24
- ## Commands
25
-
26
- ### Authentication
27
-
28
- ```bash
29
- pc login # Login via browser authorization
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
- Supported tools (auto-detected during setup):
42
- - **Claude Code** - via Stop hooks
43
- - **Codex CLI** - via notify hook
44
- - **Gemini CLI** - via BeforeAgent hook
45
-
46
- Coming soon:
47
- - Cursor
48
- - Windsurf
49
- - Aider
50
-
51
- ### Manual Capture
52
-
53
- ```bash
54
- # Save a prompt manually
55
- pc save -m "Your prompt text here"
56
-
57
- # Open editor to write prompt
58
- pc save
59
-
60
- # Specify tool and model
61
- pc save -m "prompt" -t aider -M gpt-4
62
- ```
63
-
64
- ### Push Prompts
65
-
66
- ```bash
67
- # Fetch and display a prompt by slug
68
- pc push my-project/useful-prompt
69
- ```
70
-
71
- ### Configuration
72
-
73
- ```bash
74
- # View/change capture level
75
- pc config
76
-
77
- # Set capture level directly
78
- pc config --level minimal # Tool + timestamp only
79
- pc config --level standard # + working directory, git repo/branch
80
- pc config --level rich # + session ID, git commit, remote URL
81
- ```
82
-
83
- ### Update
84
-
85
- ```bash
86
- pc update # Update to latest version
87
- ```
88
-
89
- ## Capture Levels
90
-
91
- Control how much context is captured with your prompts:
92
-
93
- | Level | Captured Data |
94
- |-------|--------------|
95
- | `minimal` | Tool, timestamp |
96
- | `standard` | + Working directory, git repo, git branch |
97
- | `rich` | + Session ID, git commit hash, remote URL |
98
-
99
- ## Environment
100
-
101
- Configuration is stored in:
102
- - macOS: `~/Library/Preferences/promptcellar-nodejs/`
103
- - Linux: `~/.config/promptcellar-nodejs/`
104
- - Windows: `%APPDATA%/promptcellar-nodejs/`
105
-
106
- ## API
107
-
108
- The CLI communicates with your Prompt Cellar instance. Get your API key from:
109
-
110
- Settings > API Keys in your Prompt Cellar dashboard.
111
-
112
- ## License
113
-
114
- MIT
@@ -1,118 +0,0 @@
1
- # wa-auth Integration Design
2
-
3
- ## Overview
4
-
5
- Migrate pc-cli authentication from prompt-registry's removed device auth endpoints to wa-auth, and add client-side encryption so captured prompts match prompt-registry's zero-knowledge architecture.
6
-
7
- ## Login Flow
8
-
9
- Four stages:
10
-
11
- 1. **Device code request** -- CLI calls `POST /device/code` on wa-auth with `{ client_id: "prompts-prod" }`. Gets back `device_code`, `user_code`, and `verification_url`.
12
-
13
- 2. **User approval** -- User opens the verification URL in browser, authenticates with their passkey, approves the device. CLI polls `POST /device/poll` with `{ device_code }`.
14
-
15
- 3. **JWT received** -- On approval, wa-auth returns `{ status: "approved", access_token: "<JWT>", user_id: "..." }`. The JWT has `aud: "prompts-prod"`, `sub: "<user_id>"`, standard exp/iss claims.
16
-
17
- 4. **API key exchange** -- CLI calls `POST /api/v1/auth/device-token` on prompt-registry with `Authorization: Bearer <JWT>` and `{ device_name: "Linux - myhostname" }`. Prompt-registry validates the JWT, auto-revokes any previous key with the same device name, creates a new `pk_*` API key, and returns:
18
-
19
- ```json
20
- {
21
- "api_key": "pk_...",
22
- "email": "user@example.com",
23
- "vault": {
24
- "available": true,
25
- "encrypted_key": "base64url(...)"
26
- }
27
- }
28
- ```
29
-
30
- If `vault.available` is false, the CLI warns that capture requires vault setup at the web UI.
31
-
32
- ## Changes to wa-auth
33
-
34
- Two modifications to the device auth flow:
35
-
36
- 1. **`POST /device/code` accepts `client_id`** -- Validates that `client_id` exists in `OIDC_CLIENTS`. Stores `client_id` on the `DeviceAuthRequest` record (new column). Rejects unknown client IDs.
37
-
38
- 2. **`POST /device/poll` returns a JWT on approval** -- When status is `approved`, generates a JWT with `sub: user_id`, `aud: client_id` (from the stored device request), `iss: account.weldedanvil.com`, and standard `iat`/`exp`. Returns it as `access_token` alongside `status` and `user_id`.
39
-
40
- The `DeviceAuthRequest` model gets one new field:
41
-
42
- ```
43
- client_id: String(64) -- stored from the initial code request
44
- ```
45
-
46
- No other wa-auth changes needed.
47
-
48
- ## Changes to prompt-registry
49
-
50
- One new endpoint:
51
-
52
- **`POST /api/v1/auth/device-token`** -- Exchanges a wa-auth JWT for a `pk_*` API key.
53
-
54
- - Reads JWT from `Authorization: Bearer <jwt>` header
55
- - Validates with `verify_account_token()` (existing function)
56
- - Calls `create_or_get_user(sub, email)` to resolve the user
57
- - Reads `device_name` from the request body
58
- - Deletes any existing `UserApiKey` with the same `user_id` and `name` (auto-revoke)
59
- - Creates a new `UserApiKey` with `generate_api_key()`, stores the hash
60
- - Returns the plaintext key, user email, and vault status
61
-
62
- Vault status is determined by checking if a vault record exists for the user. If it does, returns `encrypted_metadata`. If not, `vault.available` is false.
63
-
64
- No changes to existing endpoints or middleware.
65
-
66
- ## Changes to pc-cli
67
-
68
- ### `src/commands/login.js` -- Rewritten login flow
69
-
70
- - Calls wa-auth `POST /device/code` with `client_id`
71
- - Polls wa-auth `POST /device/poll`
72
- - On approval, exchanges JWT at prompt-registry `POST /api/v1/auth/device-token` with device name
73
- - Stores API key, vault key, and account URL in config
74
- - Warns if vault not available
75
-
76
- ### `src/lib/config.js` -- New config fields
77
-
78
- - `accountUrl` -- wa-auth base URL (default `https://account.weldedanvil.com`)
79
- - `vaultKey` -- encrypted vault metadata from login (empty string default)
80
- - `vaultAvailable` -- boolean, whether capture is enabled
81
-
82
- ### `src/lib/crypto.js` -- New file
83
-
84
- - Derives AES-256-GCM key from vault metadata
85
- - `encryptPrompt(plaintext, key)` returns `{ encrypted_content, content_iv }`
86
- - Uses Node.js built-in `crypto` module (no new dependencies)
87
-
88
- ### Hook scripts -- Updated capture payload
89
-
90
- `hooks/prompt-capture.js`, `codex-capture.js`, `gemini-capture.js`:
91
-
92
- - Check `vaultAvailable` before attempting capture
93
- - Encrypt prompt content before sending
94
- - Send `{ encrypted_content, content_iv, context_hash }` instead of plaintext
95
-
96
- ### `src/lib/api.js` -- No changes
97
-
98
- Already sends `Authorization: Bearer <key>` which works with `pk_*` keys.
99
-
100
- ## Error Handling
101
-
102
- | Scenario | Behavior |
103
- |----------|----------|
104
- | Expired JWT during key exchange | 401 from prompt-registry, CLI says re-run `pc login` |
105
- | Vault not set up | Login succeeds with warning. Hooks silently skip capture. `pc save` shows actionable error. |
106
- | Vault set up after login | Re-run `pc login` to pick up vault key |
107
- | API key revoked from web UI | 401 on API calls, existing "Not logged in" error |
108
- | wa-auth down during login | Connection error, no partial state saved |
109
- | prompt-registry down during exchange | JWT expires, re-run `pc login` |
110
-
111
- ## Out of Scope
112
-
113
- - Search index encryption from CLI (web UI concern)
114
- - `pc push` decryption (separate effort)
115
- - Vault creation from CLI (stays in web UI)
116
- - Token refresh (API keys don't expire)
117
- - Migration of existing plaintext captures
118
- - Changes to `pc setup` (hook registration unchanged)