@jive-ai/cli 0.0.2 → 0.0.4

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/dist/index.mjs CHANGED
@@ -218,13 +218,13 @@ async function signupCommand() {
218
218
  {
219
219
  type: "password",
220
220
  name: "confirmPassword",
221
- message: "Confirm password:",
222
- validate: (value, prev) => {
223
- if (value !== prev.password) return "Passwords do not match";
224
- return true;
225
- }
221
+ message: "Confirm password:"
226
222
  }
227
223
  ]);
224
+ if (response.password !== response.confirmPassword) {
225
+ console.log(chalk.yellow("\n❌ Passwords do not match"));
226
+ return;
227
+ }
228
228
  if (!response.email || !response.password) {
229
229
  console.log(chalk.yellow("\n❌ Signup cancelled"));
230
230
  return;
@@ -567,172 +567,6 @@ async function addToGitignore(pattern) {
567
567
  const CLAUDE_PLUGINS_DIR = path.join(process.cwd(), ".claude", "plugins");
568
568
  const JIVE_CONFIG_DIR = path.join(process.cwd(), ".jive");
569
569
  const JIVE_CONFIG_PATH = path.join(JIVE_CONFIG_DIR, "config.json");
570
- async function installTelemetryPlugin(apiKey, apiUrl) {
571
- await fs.mkdir(CLAUDE_PLUGINS_DIR, { recursive: true });
572
- const pluginSourceDir = path.join(process.cwd(), "plugins", "jive-mcp-telemetry");
573
- const pluginDestDir = path.join(CLAUDE_PLUGINS_DIR, "jive-mcp-telemetry");
574
- let pluginExists = false;
575
- try {
576
- await fs.access(pluginSourceDir);
577
- pluginExists = true;
578
- } catch {
579
- pluginExists = false;
580
- }
581
- if (pluginExists) await copyDirectory(pluginSourceDir, pluginDestDir);
582
- else await createMinimalPlugin(pluginDestDir);
583
- await fs.mkdir(JIVE_CONFIG_DIR, { recursive: true });
584
- let config = {};
585
- try {
586
- const existingConfig = await fs.readFile(JIVE_CONFIG_PATH, "utf-8");
587
- config = JSON.parse(existingConfig);
588
- } catch (error) {
589
- if (error.code !== "ENOENT") throw error;
590
- }
591
- config.apiUrl = apiUrl;
592
- config.apiKey = apiKey;
593
- await fs.writeFile(JIVE_CONFIG_PATH, JSON.stringify(config, null, 2));
594
- }
595
- async function copyDirectory(src, dest) {
596
- await fs.mkdir(dest, { recursive: true });
597
- const entries = await fs.readdir(src, { withFileTypes: true });
598
- for (const entry of entries) {
599
- const srcPath = path.join(src, entry.name);
600
- const destPath = path.join(dest, entry.name);
601
- if (entry.isDirectory()) await copyDirectory(srcPath, destPath);
602
- else await fs.copyFile(srcPath, destPath);
603
- }
604
- }
605
- async function createMinimalPlugin(pluginDir) {
606
- await fs.mkdir(path.join(pluginDir, ".claude-plugin"), { recursive: true });
607
- await fs.mkdir(path.join(pluginDir, "hooks"), { recursive: true });
608
- await fs.mkdir(path.join(pluginDir, "scripts"), { recursive: true });
609
- await fs.writeFile(path.join(pluginDir, ".claude-plugin", "plugin.json"), JSON.stringify({
610
- name: "jive-mcp-telemetry",
611
- version: "1.0.0",
612
- description: "Captures subagent execution traces and tool calls for Jive",
613
- author: {
614
- name: "Jive",
615
- url: "https://github.com/jive-mcp"
616
- },
617
- license: "MIT",
618
- keywords: [
619
- "subagents",
620
- "telemetry",
621
- "monitoring",
622
- "debugging"
623
- ],
624
- hooks: "../hooks/hooks.json"
625
- }, null, 2));
626
- await fs.writeFile(path.join(pluginDir, "hooks", "hooks.json"), JSON.stringify({ hooks: { PostToolUse: [{
627
- matcher: ".*",
628
- hooks: [{
629
- type: "command",
630
- command: "node ${CLAUDE_PLUGIN_ROOT}/scripts/capture-tool-use.js"
631
- }]
632
- }] } }, null, 2));
633
- await fs.writeFile(path.join(pluginDir, "scripts", "capture-tool-use.js"), `#!/usr/bin/env node
634
-
635
- /**
636
- * Jive Telemetry Capture Script
637
- *
638
- * This script runs as a PostToolUse hook to capture tool calls made during
639
- * subagent execution and send them to the Jive API for tracking.
640
- */
641
-
642
- const fs = require('fs');
643
- const path = require('path');
644
- const https = require('https');
645
- const http = require('http');
646
-
647
- // Find the project's .jive/config.json by searching upward from cwd
648
- function findProjectConfig() {
649
- let dir = process.cwd();
650
- while (dir !== path.parse(dir).root) {
651
- const configPath = path.join(dir, '.jive', 'config.json');
652
- if (fs.existsSync(configPath)) {
653
- return configPath;
654
- }
655
- dir = path.dirname(dir);
656
- }
657
- return null;
658
- }
659
-
660
- const CONFIG_FILE = findProjectConfig();
661
- const STATE_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.jive-mcp', 'telemetry');
662
- const LOG_FILE = path.join(STATE_DIR, 'telemetry.log');
663
-
664
- // Ensure state directory exists
665
- try {
666
- if (!fs.existsSync(STATE_DIR)) {
667
- fs.mkdirSync(STATE_DIR, { recursive: true });
668
- }
669
- } catch (err) {
670
- process.exit(0);
671
- }
672
-
673
- function log(message) {
674
- try {
675
- const timestamp = new Date().toISOString();
676
- fs.appendFileSync(LOG_FILE, \`[\${timestamp}] \${message}\\n\`);
677
- } catch (err) {
678
- // Ignore logging errors
679
- }
680
- }
681
-
682
- function loadConfig() {
683
- try {
684
- if (!CONFIG_FILE || !fs.existsSync(CONFIG_FILE)) {
685
- log('Config file not found, telemetry disabled');
686
- return null;
687
- }
688
- const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
689
- return config;
690
- } catch (err) {
691
- log(\`Error loading config: \${err.message}\`);
692
- return null;
693
- }
694
- }
695
-
696
- function getCurrentInvocation() {
697
- try {
698
- const stateFile = path.join(STATE_DIR, 'current-invocation.json');
699
- if (!fs.existsSync(stateFile)) {
700
- return null;
701
- }
702
- const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
703
- const age = Date.now() - state.timestamp;
704
- if (age > 3600000) {
705
- fs.unlinkSync(stateFile);
706
- return null;
707
- }
708
- return state;
709
- } catch (err) {
710
- log(\`Error reading invocation state: \${err.message}\`);
711
- return null;
712
- }
713
- }
714
-
715
- function getNextSequenceNumber(invocationId) {
716
- const seqFile = path.join(STATE_DIR, \`seq-\${invocationId}.txt\`);
717
- try {
718
- let seq = 1;
719
- if (fs.existsSync(seqFile)) {
720
- seq = parseInt(fs.readFileSync(seqFile, 'utf8')) + 1;
721
- }
722
- fs.writeFileSync(seqFile, seq.toString());
723
- return seq;
724
- } catch (err) {
725
- log(\`Error managing sequence number: \${err.message}\`);
726
- return 1;
727
- }
728
- }
729
-
730
- // Simplified version - full implementation in the source repository
731
- log('Telemetry hook triggered (minimal version)');
732
- process.exit(0);
733
- `);
734
- await fs.chmod(path.join(pluginDir, "scripts", "capture-tool-use.js"), 493);
735
- }
736
570
  async function getPluginStatus() {
737
571
  const pluginPath = path.join(CLAUDE_PLUGINS_DIR, "jive-mcp-telemetry");
738
572
  const configPath = JIVE_CONFIG_PATH;
@@ -833,7 +667,7 @@ async function initCommand() {
833
667
  }
834
668
  spinner.text = "Scanning for existing subagents...";
835
669
  const subagentFiles = await getSubagentFiles();
836
- let uploadedSubagents = 0;
670
+ let uploadedSubagents = [];
837
671
  if (subagentFiles.length > 0) {
838
672
  spinner.text = `Found ${subagentFiles.length} subagent(s), uploading...`;
839
673
  for (const subagent of subagentFiles) {
@@ -845,7 +679,10 @@ async function initCommand() {
845
679
  prompt: subagent.prompt
846
680
  });
847
681
  await updateSubagentJiveId(subagent.name, created.id);
848
- uploadedSubagents++;
682
+ uploadedSubagents.push({
683
+ name: subagent.name,
684
+ id: created.id
685
+ });
849
686
  } catch (error) {
850
687
  console.warn(chalk.gray(`\nWarning: Could not upload subagent "${subagent.name}": ${error.message}`));
851
688
  }
@@ -853,59 +690,38 @@ async function initCommand() {
853
690
  }
854
691
  spinner.text = "Adding Jive server to .mcp.json...";
855
692
  await addMcpServer("jive-mcp", {
856
- command: "npx",
857
- args: ["-y", "@jive/mcp-server"],
693
+ command: "jive mcp start",
858
694
  env: {
859
- JIVE_API_URL: process.env.JIVE_API_URL || "http://localhost:5173",
860
695
  JIVE_TEAM_ID: teamId,
861
696
  JIVE_AUTH_TOKEN: credentials.token
862
697
  }
863
698
  });
699
+ spinner.text = "Removing uploaded MCP servers from local .mcp.json...";
700
+ if (mcpConfig?.mcpServers) {
701
+ for (const name of Object.keys(mcpConfig.mcpServers)) if (uploadedSubagents.some((subagent) => subagent.name === name)) await removeMcpServer(name);
702
+ }
864
703
  spinner.text = "Creating subagent-runner...";
865
704
  await createSubagentRunner();
866
705
  spinner.text = "Updating .gitignore...";
867
- await addToGitignore(".jive/config.json");
868
- await addToGitignore(".jive/sync.json");
869
- await addToGitignore(".claude/plugins/");
706
+ await addToGitignore(".jive");
870
707
  spinner.text = "Creating project configuration...";
871
708
  await saveProjectConfig({
872
709
  teamId,
873
710
  activeTeamId: teamId,
874
711
  lastSync: (/* @__PURE__ */ new Date()).toISOString()
875
712
  });
876
- spinner.text = "Installing Claude Code telemetry plugin...";
877
- const apiUrl = process.env.JIVE_API_URL || "http://localhost:5173";
878
- try {
879
- await installTelemetryPlugin(credentials.token, apiUrl);
880
- spinner.text = "Telemetry plugin installed";
881
- } catch (error) {
882
- console.warn(chalk.yellow(`\nWarning: Could not install telemetry plugin: ${error.message}`));
883
- }
884
713
  spinner.succeed(chalk.green("Project initialized successfully!"));
885
714
  console.log(chalk.bold("\n📦 Summary:\n"));
886
715
  if (uploadedServers > 0) console.log(chalk.green(` ✓ Uploaded ${uploadedServers} MCP server(s) to team`));
887
- if (uploadedSubagents > 0) console.log(chalk.green(` ✓ Uploaded ${uploadedSubagents} subagent(s) to team`));
716
+ if (uploadedSubagents.length > 0) console.log(chalk.green(` ✓ Uploaded ${uploadedSubagents.length} subagent(s) to team`));
888
717
  console.log(chalk.green(" ✓ Added @jive/mcp server to .mcp.json"));
889
718
  console.log(chalk.green(" ✓ Created .claude/agents/subagent-runner.md"));
890
719
  console.log(chalk.green(" ✓ Created .jive/config.json"));
891
720
  console.log(chalk.green(" ✓ Updated .gitignore"));
892
- const pluginStatus = await getPluginStatus();
893
- if (pluginStatus.installed) console.log(chalk.green(" ✓ Installed telemetry plugin to .claude/plugins/"));
894
721
  console.log(chalk.bold("\n✨ Next Steps:\n"));
895
- console.log(chalk.white(" • Restart Claude Code to load the Jive server and telemetry plugin"));
722
+ console.log(chalk.white(" • Restart Claude Code to load the Jive MCP server"));
896
723
  console.log(chalk.white(" • Run"), chalk.cyan("jive sync"), chalk.white("to sync resources"));
897
724
  console.log(chalk.white(" • Run"), chalk.cyan("jive status"), chalk.white("to check sync status"));
898
- if (pluginStatus.installed) {
899
- console.log(chalk.bold("\n🔍 Subagent Visibility:\n"));
900
- console.log(chalk.white(" • The telemetry plugin will track subagent executions"));
901
- console.log(chalk.white(" • View execution traces in the web UI (View Executions button)"));
902
- console.log(chalk.white(" • Generate an API key in the web UI for full telemetry support"));
903
- console.log(chalk.gray(" (Settings > API Keys > Create New Key)\n"));
904
- } else {
905
- console.log(chalk.yellow("\n⚠️ Telemetry plugin installation failed"));
906
- console.log(chalk.white(" • Subagent execution tracking will not work"));
907
- console.log(chalk.white(" • See SUBAGENT_VISIBILITY.md for manual installation\n"));
908
- }
909
725
  } catch (error) {
910
726
  spinner.fail(chalk.red("Initialization failed"));
911
727
  console.error(chalk.red(`\n❌ ${error.message || "An error occurred"}`));
package/docs/auth.md ADDED
@@ -0,0 +1,378 @@
1
+ # Authentication Commands
2
+
3
+ Jive CLI requires authentication to access team resources and collaborate with team members. This guide covers all authentication-related commands.
4
+
5
+ ## Overview
6
+
7
+ Authentication in Jive uses token-based authentication. After logging in or signing up, your credentials are securely stored in `~/.jive/credentials.json` with restricted file permissions (mode 0600).
8
+
9
+ ## Commands
10
+
11
+ ### `jive signup`
12
+
13
+ Create a new Jive account.
14
+
15
+ **Usage:**
16
+ ```bash
17
+ jive signup
18
+ ```
19
+
20
+ **Interactive Prompts:**
21
+ - **Email:** Your email address for account registration
22
+ - **Password:** Account password (minimum 8 characters)
23
+ - **Confirm Password:** Re-enter password for verification
24
+
25
+ **Process:**
26
+ 1. Prompts for email and password
27
+ 2. Validates password requirements (minimum 8 characters)
28
+ 3. Calls the Jive API signup endpoint (`/api/auth/signup`)
29
+ 4. Automatically saves authentication credentials
30
+ 5. Displays success message with suggested next steps
31
+
32
+ **Output:**
33
+ ```
34
+ Account created successfully!
35
+ Credentials saved to /Users/username/.jive/credentials.json
36
+
37
+ Next steps:
38
+ 1. Create a team: jive team create
39
+ 2. Initialize Jive in your project: jive init
40
+ ```
41
+
42
+ **Credentials Stored:**
43
+ ```json
44
+ {
45
+ "token": "auth-token-here",
46
+ "userId": "user-123",
47
+ "email": "user@example.com"
48
+ }
49
+ ```
50
+
51
+ **Error Conditions:**
52
+ - Email already registered
53
+ - Password too short (< 8 characters)
54
+ - Passwords don't match
55
+ - Network connectivity issues
56
+
57
+ **Implementation:** `src/commands/auth.ts` (signupCommand)
58
+
59
+ ---
60
+
61
+ ### `jive login`
62
+
63
+ Authenticate with an existing Jive account.
64
+
65
+ **Usage:**
66
+ ```bash
67
+ jive login
68
+ ```
69
+
70
+ **Interactive Prompts:**
71
+ - **Email:** Your registered email address
72
+ - **Password:** Your account password
73
+
74
+ **Process:**
75
+ 1. Prompts for email and password
76
+ 2. Calls the Jive API login endpoint (`/api/auth/login`)
77
+ 3. Saves authentication credentials to `~/.jive/credentials.json`
78
+ 4. Sets file permissions to 0600 (read/write for owner only)
79
+
80
+ **Output:**
81
+ ```
82
+ Login successful!
83
+ Credentials saved to /Users/username/.jive/credentials.json
84
+ ```
85
+
86
+ **Credentials Stored:**
87
+ ```json
88
+ {
89
+ "token": "auth-token-here",
90
+ "userId": "user-123",
91
+ "email": "user@example.com"
92
+ }
93
+ ```
94
+
95
+ **Error Conditions:**
96
+ - Invalid email or password
97
+ - Account not found
98
+ - Network connectivity issues
99
+ - API server unavailable
100
+
101
+ **Security Notes:**
102
+ - Credentials file is created with mode 0600 (only readable by owner)
103
+ - Never share your credentials file
104
+ - The authentication token is used for all API requests
105
+
106
+ **Implementation:** `src/commands/auth.ts` (loginCommand)
107
+
108
+ ---
109
+
110
+ ### `jive logout`
111
+
112
+ Clear authentication credentials and log out.
113
+
114
+ **Usage:**
115
+ ```bash
116
+ jive logout
117
+ ```
118
+
119
+ **Interactive Prompts:**
120
+ - **Confirmation:** "Are you sure you want to logout?" (Y/n)
121
+
122
+ **Process:**
123
+ 1. Checks if user is currently logged in
124
+ 2. Prompts for confirmation
125
+ 3. Deletes `~/.jive/credentials.json` file
126
+ 4. Displays success message
127
+
128
+ **Output:**
129
+ ```
130
+ Logged out successfully
131
+ ```
132
+
133
+ **Notes:**
134
+ - This only removes local credentials; it doesn't invalidate the token on the server
135
+ - You can log back in at any time with `jive login`
136
+ - Active team configurations in projects remain intact
137
+
138
+ **Error Conditions:**
139
+ - Not currently logged in (displays "Not logged in" message)
140
+ - User cancels confirmation
141
+
142
+ **Implementation:** `src/commands/auth.ts` (logoutCommand)
143
+
144
+ ---
145
+
146
+ ## Authentication Flow
147
+
148
+ ### First-Time Setup
149
+
150
+ 1. **Sign up for an account:**
151
+ ```bash
152
+ jive signup
153
+ ```
154
+
155
+ 2. **Create or join a team:**
156
+ ```bash
157
+ jive team create
158
+ ```
159
+ Or accept an email invitation to join an existing team.
160
+
161
+ 3. **Initialize in a project:**
162
+ ```bash
163
+ jive init
164
+ ```
165
+
166
+ ### Returning User
167
+
168
+ 1. **Log in:**
169
+ ```bash
170
+ jive login
171
+ ```
172
+
173
+ 2. **Start using Jive commands:**
174
+ ```bash
175
+ jive team list
176
+ jive subagents pull
177
+ jive mcp pull
178
+ ```
179
+
180
+ ## Credentials Storage
181
+
182
+ ### Location
183
+
184
+ Credentials are stored in:
185
+ ```
186
+ ~/.jive/credentials.json
187
+ ```
188
+
189
+ **Absolute paths:**
190
+ - macOS/Linux: `/Users/username/.jive/credentials.json`
191
+ - Windows: `C:\Users\username\.jive\credentials.json`
192
+
193
+ ### Format
194
+
195
+ ```json
196
+ {
197
+ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
198
+ "userId": "user-abc123",
199
+ "email": "user@example.com"
200
+ }
201
+ ```
202
+
203
+ ### Security
204
+
205
+ - File permissions set to 0600 (owner read/write only)
206
+ - Token is used in `X-API-Key` header for all API requests
207
+ - Automatically injected by the API client
208
+
209
+ **Never commit credentials to version control!**
210
+
211
+ ## API Integration
212
+
213
+ ### Authentication Header
214
+
215
+ All authenticated requests include:
216
+ ```http
217
+ X-API-Key: <token-from-credentials>
218
+ ```
219
+
220
+ ### Token Handling
221
+
222
+ The API client (`src/lib/api-client.ts`) automatically:
223
+ - Reads credentials from `~/.jive/credentials.json`
224
+ - Injects the `X-API-Key` header
225
+ - Handles 401 Unauthorized responses by prompting re-authentication
226
+
227
+ ### Session Management
228
+
229
+ - Tokens do not expire (currently)
230
+ - If you receive authentication errors, try logging out and back in:
231
+ ```bash
232
+ jive logout
233
+ jive login
234
+ ```
235
+
236
+ ## Troubleshooting
237
+
238
+ ### "Not authenticated" Error
239
+
240
+ **Symptom:** Commands fail with "Please login first: jive login"
241
+
242
+ **Solutions:**
243
+ 1. Check if credentials file exists:
244
+ ```bash
245
+ ls ~/.jive/credentials.json
246
+ ```
247
+
248
+ 2. Log in again:
249
+ ```bash
250
+ jive login
251
+ ```
252
+
253
+ 3. If signup is needed:
254
+ ```bash
255
+ jive signup
256
+ ```
257
+
258
+ ### "Invalid credentials" Error
259
+
260
+ **Symptom:** Login fails with incorrect email/password
261
+
262
+ **Solutions:**
263
+ 1. Verify email address is correct
264
+ 2. Reset password (if password reset is implemented)
265
+ 3. Sign up for a new account if needed:
266
+ ```bash
267
+ jive signup
268
+ ```
269
+
270
+ ### Credentials File Missing
271
+
272
+ **Symptom:** `~/.jive/credentials.json` doesn't exist
273
+
274
+ **Solutions:**
275
+ 1. Log in to recreate the file:
276
+ ```bash
277
+ jive login
278
+ ```
279
+
280
+ 2. Check file permissions:
281
+ ```bash
282
+ ls -la ~/.jive/credentials.json
283
+ ```
284
+ Should show `-rw-------` (0600 permissions)
285
+
286
+ ### 401 Unauthorized Errors
287
+
288
+ **Symptom:** API requests fail with 401 status
289
+
290
+ **Solutions:**
291
+ 1. Log out and log back in:
292
+ ```bash
293
+ jive logout
294
+ jive login
295
+ ```
296
+
297
+ 2. Verify credentials are valid:
298
+ ```bash
299
+ cat ~/.jive/credentials.json
300
+ ```
301
+
302
+ 3. Check API connectivity:
303
+ ```bash
304
+ jive doctor
305
+ ```
306
+
307
+ ## Environment Variables
308
+
309
+ ### `JIVE_API_URL`
310
+
311
+ Override the default API URL:
312
+
313
+ ```bash
314
+ export JIVE_API_URL=https://your-custom-api.com
315
+ jive login
316
+ ```
317
+
318
+ **Default:** `https://next.getjive.app`
319
+
320
+ This affects all API endpoints:
321
+ - `/api/auth/login`
322
+ - `/api/auth/signup`
323
+ - `/api/teams/*`
324
+ - `/api/subagents/*`
325
+ - `/api/mcp-servers/*`
326
+
327
+ ## Security Best Practices
328
+
329
+ 1. **Never share your credentials file**
330
+ - Don't commit to version control
331
+ - Don't share via chat or email
332
+ - Don't copy to shared locations
333
+
334
+ 2. **Use strong passwords**
335
+ - Minimum 8 characters (enforced)
336
+ - Mix of letters, numbers, and symbols (recommended)
337
+ - Unique password for Jive (recommended)
338
+
339
+ 3. **Protect your credentials file**
340
+ - File permissions are automatically set to 0600
341
+ - Don't modify permissions to make it world-readable
342
+ - Keep backups secure
343
+
344
+ 4. **Log out when done**
345
+ - On shared machines, always log out:
346
+ ```bash
347
+ jive logout
348
+ ```
349
+
350
+ 5. **Monitor access**
351
+ - Review team member list regularly
352
+ - Remove users who no longer need access
353
+
354
+ ## Related Commands
355
+
356
+ - [`jive team create`](./team.md#jive-team-create) - Create a team after signup
357
+ - [`jive team list`](./team.md#jive-team-list) - List teams (requires authentication)
358
+ - [`jive init`](./init.md#jive-init) - Initialize project (requires authentication)
359
+ - [`jive doctor`](./init.md#jive-doctor) - Verify authentication and setup
360
+
361
+ ## Implementation Details
362
+
363
+ **Source Files:**
364
+ - Main implementation: `src/commands/auth.ts`
365
+ - Configuration helpers: `src/lib/config.ts`
366
+ - API client: `src/lib/api-client.ts`
367
+
368
+ **Key Functions:**
369
+ - `signupCommand()` - Handles user registration
370
+ - `loginCommand()` - Handles user authentication
371
+ - `logoutCommand()` - Clears credentials
372
+ - `loadCredentials()` - Reads credentials file
373
+ - `saveCredentials()` - Writes credentials file securely
374
+ - `requireAuth()` - Ensures user is authenticated (exits if not)
375
+
376
+ **API Endpoints:**
377
+ - `POST /api/auth/signup` - Create new account
378
+ - `POST /api/auth/login` - Authenticate existing account