WIP: A simple cli for daily tangled use cases and AI integration. This is for my personal use right now, but happy if others get mileage from it! :)
1import { execSync } from 'node:child_process';
2import type { TangledApiClient } from '../lib/api-client.js';
3import { KeychainAccessError } from '../lib/session.js';
4
5/**
6 * Validate that the client is authenticated and has an active session
7 * @throws Error if not authenticated or no session found
8 * @returns The current session with did and handle
9 */
10export async function requireAuth(client: TangledApiClient): Promise<{
11 did: string;
12 handle: string;
13}> {
14 if (!client.isAuthenticated()) {
15 throw new Error('Must be authenticated. Run "tangled auth login" first.');
16 }
17
18 const session = client.getSession();
19 if (!session) {
20 throw new Error('No active session found');
21 }
22
23 return session;
24}
25
26function tryUnlockKeychain(): boolean {
27 if (process.platform !== 'darwin') return false;
28 try {
29 execSync('security unlock-keychain', { stdio: 'inherit' });
30 return true;
31 } catch {
32 return false;
33 }
34}
35
36/**
37 * Resume session and ensure the client is authenticated.
38 * On macOS, if the keychain is locked, attempts to unlock it interactively
39 * via `security unlock-keychain` before falling back to an error message.
40 * Exits the process with a clear error message if authentication fails.
41 */
42export async function ensureAuthenticated(client: TangledApiClient): Promise<void> {
43 try {
44 const authenticated = await client.resumeSession();
45 if (!authenticated) {
46 console.error('✗ Not authenticated. Run "tangled auth login" first.');
47 process.exit(1);
48 }
49 } catch (error) {
50 if (error instanceof KeychainAccessError) {
51 const unlocked = tryUnlockKeychain();
52 if (unlocked) {
53 try {
54 const retried = await client.resumeSession();
55 if (retried) return;
56 } catch {
57 // fall through to error message
58 }
59 }
60 console.error('✗ Cannot access keychain. Please unlock your Mac keychain and try again.');
61 console.error(' You can unlock it manually with: security unlock-keychain');
62 process.exit(1);
63 }
64 throw error;
65 }
66}