Skip to content

Add session management to account-cli#260

Merged
fan-zhang-sv merged 8 commits intomasterfrom
felix/cli-session-management
Mar 25, 2026
Merged

Add session management to account-cli#260
fan-zhang-sv merged 8 commits intomasterfrom
felix/cli-session-management

Conversation

@fan-zhang-sv
Copy link
Copy Markdown
Collaborator

@fan-zhang-sv fan-zhang-sv commented Mar 24, 2026

Summary

Adds session management to @base-org/account-cli, enabling the CLI to persist, resolve, and destroy authenticated sessions for three distinct operating modes. This is the foundational layer that future commands (login, send, balance, etc.) will build on to determine who is acting and how they're authorized.

Motivation

The CLI needs a way to remember authentication state between invocations. A user may authenticate as a direct account operator, create a CLI-managed smart wallet sub-account, or bring their own external EOA. Each of these has different authorization semantics, so the session model must be mode-aware and support disambiguation when multiple sessions coexist.

Session modes

Mode Key field Description
operator account Direct owner access to the base account. No sub-account or spend permissions are applied.
smart-wallet subAccount CLI-managed smart wallet sub-account, created as a child of the user's base account. Includes a delegated signer address.
external-eoa eoa Externally-owned account brought by an agent. The EOA is not managed by the CLI — registered as a sub-account for policy scoping.

All session types carry a version field (currently 1) for future schema migrations, a createdAt timestamp, and an optional CAIP-2 chainId (for smart-wallet and external-eoa).

Session resolution

When a command needs the active session, resolveSession() applies this precedence:

  1. BASE_SESSION env var — if set, matches against the key field of all sessions on disk. Returns resolvedVia: "env_var".
  2. Auto-select — if exactly one session exists, selects it. Returns resolvedVia: "auto_select".
  3. Ambiguity error — if multiple sessions exist without BASE_SESSION, throws MULTIPLE_SESSIONS with a list of available mode:address labels.

New CLI commands

  • session list [--json] — enumerate all sessions on disk with mode-appropriate summaries
  • session info [--json] — resolve and display the active session (includes resolved_via, chain_id, signer where applicable)
  • session destroy <address> [--mode <mode>] [--json] — destroy a specific session by its key address
  • session destroy --all [--json] — destroy all sessions

Security

  • File permissions — sessions dir is created with 0o700, session files with 0o600. Both listSessions and loadSession verify permissions before reading and throw INSECURE_PERMISSIONS if group/world bits are set.
  • Secure deletionsecureDelete() overwrites file contents with crypto.randomBytes before unlink, preventing recovery from disk.
  • Atomic writeswriteSession() writes to a temp file then rename()s into place to avoid partial-write corruption.
  • Path traversal protectionsessionFile() resolves the path and rejects any identifier that escapes the sessions directory.
  • Audit logging — all session create/destroy operations are appended to ~/.base-account/logs/audit.jsonl with timestamp, operation, mode, and identifier.

File layout

~/.base-account/
├── sessions/
│   ├── operator-0xAbc.json
│   ├── smart-wallet-0xDef.json
│   └── external-eoa-0xGhi.json
├── keys/                          # (reserved for future key storage)
└── logs/
    └── audit.jsonl

Files changed (17 files, +1,614 lines)

File Description
types/session.ts Session type definitions, SESSION_VERSION, SESSION_MODES
types/caip.ts CAIP-2 ChainId type, ParsedChainId, KNOWN_CHAINS aliases
types/audit.ts AuditOperation and AuditEntry types
types/index.ts Re-exports for new types
core/paths.ts Centralized path helpers with BASE_ACCOUNT_DIR override and path traversal guard
core/session.ts Session CRUD: listSessions, loadSession, writeSession, resolveSession, destroySession, destroyAllSessions
core/audit/index.ts appendAuditLog() — append-only JSONL audit writer
commands/session/index.ts CLI command registration for session list, info, destroy
utils/caip.ts parseChainId, isValidChainId, resolveChainId helpers
utils/permissions.ts verifyDirectoryPermissions, verifyFilePermissions
utils/secure-delete.ts secureDelete() — random-overwrite + unlink
index.ts Register session commands on the program
core/session.test.ts Unit tests for session CRUD, resolution, permissions, audit (614 lines)
utils/caip.test.ts Unit tests for CAIP-2 parsing/validation (60 lines)
utils/permissions.test.ts Unit tests for permission verification (144 lines)
utils/secure-delete.test.ts Unit tests for secure deletion (95 lines)
commands/session/session.integration.test.ts End-to-end integration tests via execFileSync (217 lines)

Test plan

  • Unit tests: session CRUD, resolution logic, schema versioning, CAIP-2 utilities, permission verification, secure deletion, audit logging (~913 lines across 4 test files)
  • Integration tests: CLI end-to-end for all session commands across all modes (~217 lines)
  • Manual smoke tests (results below)

Manual smoke test results

Empty state — no sessions on disk

Screenshot 2026-03-24 at 10 26 55 AM

Single operator session

Screenshot 2026-03-24 at 10 27 52 AM

Single smart-wallet session

Screenshot 2026-03-24 at 10 28 30 AM

Multiple sessions — ambiguity detection

Screenshot 2026-03-24 at 10 29 46 AM

BASE_SESSION env var resolution

Screenshot 2026-03-24 at 10 30 15 AM

Introduce session lifecycle (create, list, resolve, destroy) for three
modes: operator, smart-wallet, and external-eoa. Sessions are stored as
JSON files under ~/.base-account/sessions/ with owner-only permissions
(0o700/0o600) and secure deletion (random-byte overwrite before unlink).

Key additions:
- Session types with CAIP-2 chain identifiers and schema versioning
- Deterministic session resolution via BASE_SESSION env var or auto-select
- CLI commands: `session list`, `session info`, `session destroy [--all]`
- Audit logging (JSONL) for all session create/destroy operations
- Path traversal protection on session file paths
- CAIP-2 parsing/validation utilities with known chain aliases
- Comprehensive unit tests (615 lines) and integration tests (212 lines)

Made-with: Cursor
@cb-heimdall
Copy link
Copy Markdown
Collaborator

cb-heimdall commented Mar 24, 2026

✅ Heimdall Review Status

Requirement Status More Info
Reviews 2/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this not be index.test.ts?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated!

@linear
Copy link
Copy Markdown

linear bot commented Mar 24, 2026

stephancill
stephancill previously approved these changes Mar 24, 2026
Copy link
Copy Markdown
Collaborator

@spencerstock spencerstock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Changes requested · 52/100

This PR builds a solid session management foundation with good architecture — atomic writes, path traversal protection, audit logging, and comprehensive tests. However, there are two critical issues that must be fixed before merging:

  1. Secure deletion is broken: openSync(filePath, 'w') truncates the file before overwriting, so sensitive data blocks are released and never actually overwritten. Change to 'r+' flag.

  2. Windows is completely broken: Strict POSIX permission checks (0o700/0o600 equality) will always fail on Windows, making the CLI unusable on that platform. Add platform guards.

Additionally, there are several important suggestions: destroyAllSessions should not depend on listSessions() (which can throw on corrupt files), address comparisons should be case-insensitive, getBaseDir() has a subtle env var precedence bug that could break tests in CI, and the path traversal guard also uses Unix-specific path separators.

Confidence: 52/100  ·  🔴 2 critical  ·  🟡 4 suggestions

Pass 1 → Pass 2 full details

Pass 1 found 4 comments:

packages/account-cli/src/utils/secure-delete.ts:12critical

Using the 'w' flag truncates the file to 0 bytes before you write to it.

On modern filesystems, truncating a file releases its allocated data blocks back to the free space pool. When you subsequently write randomBytes(size), the filesystem allocates new blocks for this data. The original blocks containing the sensitive session data are left untouched and can be recovered using disk forensics tools.

To overwrite the existing data blocks in-place, open the file for reading and writing without truncation using the 'r+' flag:

const fd = openSync(filePath, 'r+');

Pass 2: Confirmed by reading the source. openSync(filePath, 'w') truncates the file to 0 bytes (Node.js docs: flag 'w' opens for writing, truncating the file if it exists). The subsequent writeSync(fd, randomBytes(size)) writes to newly allocated blocks, not the original ones containing sensitive data. Using 'r+' (open for reading and writing without truncation) is the correct fix. This completely defeats the stated security goal of preventing disk forensic recovery.

packages/account-cli/src/utils/permissions.ts:11critical

These strict POSIX permission checks will completely break the CLI on Windows.

Node.js on Windows does not fully map POSIX permissions. A directory created with mkdirSync or a file created with writeFileSync will typically reflect a mode like 0o666 or 0o777 when read via statSync.mode. Consequently, mode !== 0o700 and mode !== 0o600 will almost always evaluate to true, and Windows users will be permanently blocked by INSECURE_PERMISSIONS errors.

Bypass these checks entirely on Windows to ensure cross-platform compatibility:

// Add to the top of both functions
if (process.platform === 'win32') return;

Pass 2: Confirmed. Node.js on Windows does not properly support POSIX file mode bits. statSync().mode & 0o777 will return values like 0o666 or 0o777 regardless of actual Windows ACLs. The strict equality checks (mode !== 0o700 and mode !== 0o600) will always fail on Windows, making the CLI completely unusable there. The PR description doesn't mention any platform restrictions, and the package.json doesn't indicate it's POSIX-only. A process.platform === 'win32' guard is the appropriate fix.

packages/account-cli/src/core/session/index.ts:109suggestion

destroyAllSessions relies on listSessions(), which throws if any session file has invalid JSON or incorrect permissions.

This means if a single session file is corrupted or accidentally chmoded by the user, session destroy --all completely fails, breaking the user's escape hatch to reset their state.

Instead of parsing the files, just read the directory and securely delete any matching filenames directly:

export function destroyAllSessions(): string[] {
  const dir = sessionsDir();
  if (!existsSync(dir)) return [];
  // ... verify dir perms ...
  
  const labels: string[] = [];
  for (const f of readdirSync(dir)) {
    const match = SESSION_FILE_RE.exec(f);
    if (!match) continue;
    
    const mode = match[1] as SessionMode;
    const key = match[2];
    secureDelete(join(dir, f));
    appendAuditLog('session_destroy_all', mode, key);
    labels.push(`${mode}:${key}`);
  }
  return labels;
}

Pass 2: Confirmed by reading the code. destroyAllSessions() at line 113 calls listSessions(), which throws CORRUPT_SESSION on invalid JSON (line 44-45) and INSECURE_PERMISSIONS on wrong file permissions (line 40). If a user accidentally chmods a session file or a file gets corrupted, destroy --all becomes unusable — the very command that should help reset broken state. The suggested fix of using readdirSync + regex matching + direct secureDelete is sound and removes the fragile dependency.

packages/account-cli/src/commands/session/index.ts:95suggestion

Ethereum addresses are often mixed-case (checksummed) or lowercase. Comparing them with strictly case-sensitive === creates brittle UX, requiring the user to type the exact casing stored in the file.

If a user tries to destroy a session using a lowercase address but the session was saved with a checksummed address, it will throw an INVALID_INPUT error.

Convert both sides to lowercase for a robust comparison:

const target = identifier.toLowerCase();
const match = sessions.find((s) => sessionKey(s).toLowerCase() === target);

(This same suggestion applies to process.env.BASE_SESSION resolution in resolveSession).

Pass 2: Confirmed. Ethereum addresses are often in EIP-55 mixed-case checksum format or all lowercase. At line 96, sessionKey(s) === identifier uses strict equality, so 0xabc123 won't match 0xAbc123. The same issue exists in resolveSession() at line 56 where sessionKey(s) === envValue is also case-sensitive. This is a real UX problem since Ethereum addresses are case-insensitive by nature. The fix of .toLowerCase() on both sides is correct and should be applied in both locations.

Pass 2 added 2 new findings:

🆕 packages/account-cli/src/core/paths.ts:4suggestion

baseDir is initialized once at module load from process.env.BASE_ACCOUNT_DIR, but getBaseDir() also reads process.env.BASE_ACCOUNT_DIR at call time. This creates a subtle inconsistency: if the env var is set at startup, baseDir captures it. If setBaseDir() is later called (e.g., in tests), getBaseDir() will still return the env var value instead of the value set by setBaseDir(), because the env var check takes precedence.

In the integration tests this works because BASE_ACCOUNT_DIR is passed to the child process. But in unit tests, setBaseDir() works only because process.env.BASE_ACCOUNT_DIR is not set in the test process. If it were ever set (e.g., in CI), all unit tests would break because setBaseDir() would be silently ignored.

Consider making getBaseDir() simply return baseDir and only read the env var during initialization:

export function getBaseDir(): string {
  return baseDir;
}

And update the initialization to:

let baseDir = process.env.BASE_ACCOUNT_DIR || join(homedir(), '.base-account');

(which is already the case — just remove the env var check from getBaseDir()).

🆕 packages/account-cli/src/core/paths.ts:31suggestion

The path traversal guard !filePath.startsWith(${dir}/) can be bypassed on Windows where the path separator is \ not /. resolve() on Windows will produce paths like C:\Users\...\sessions\operator-foo.json, which will never start with ${dir}/ (with forward slash). This means the guard would reject all identifiers on Windows — or if you fix it to use path.sep, an attacker could use forward slashes in the identifier to escape.

Consider using path.sep or normalizing both paths:

const normalizedDir = resolve(dir) + path.sep;
if (!filePath.startsWith(normalizedDir)) {
  throw new Error('Invalid session identifier: path traversal detected');
}
🔧 Fix with prompt
A reviewer gave these comments as feedback. Validate them and fix all the ones that need to be fixed.

- [critical] packages/account-cli/src/utils/secure-delete.ts:12-15
  **Critical: `'w'` flag truncates the file, defeating secure deletion.**
  
  Using `openSync(filePath, 'w')` truncates the file to 0 bytes before writing. The OS releases the original data blocks back to the free pool, and `randomBytes(size)` is written to *newly allocated* blocks. The original sensitive data remains on disk and is recoverable with forensic tools.
  
  To overwrite the existing data blocks in-place, use the `'r+'` flag:
  ```ts
  const fd = openSync(filePath, 'r+');
  ```

- [critical] packages/account-cli/src/utils/permissions.ts:11-32
  **Critical: Strict POSIX permission checks will break the CLI on Windows.**
  
  Node.js on Windows does not map POSIX permissions correctly. `statSync().mode & 0o777` will return values like `0o666` or `0o777` regardless of the actual Windows ACLs. The strict equality checks (`mode !== 0o700` / `mode !== 0o600`) will always fail, permanently blocking Windows users with `INSECURE_PERMISSIONS` errors.
  
  Bypass these checks on Windows:
  ```ts
  if (process.platform === 'win32') return;
  ```

- [suggestion] packages/account-cli/src/core/session/index.ts:109-114
  `destroyAllSessions` depends on `listSessions()`, which throws on corrupted files or incorrect permissions. This means a single broken session file prevents `destroy --all` from working — defeating the user's escape hatch to reset state.
  
  Instead, read the directory directly and delete matching filenames without parsing:
  ```ts
  export function destroyAllSessions(): string[] {
    const dir = sessionsDir();
    if (!existsSync(dir)) return [];
    verifyDirectoryPermissions(dir);
    
    const labels: string[] = [];
    for (const f of readdirSync(dir)) {
      const match = SESSION_FILE_RE.exec(f);
      if (!match) continue;
      secureDelete(join(dir, f));
      appendAuditLog('session_destroy_all', match[1] as SessionMode, match[2]);
      labels.push(`${match[1]}:${match[2]}`);
    }
    return labels;
  }
  ```

- [suggestion] packages/account-cli/src/commands/session/index.ts:95-99
  Ethereum addresses are case-insensitive, but this comparison uses strict `===`. A user typing `0xeoa444` when the stored session has `0xEoa444` will get an `INVALID_INPUT` error.
  
  Use case-insensitive comparison:
  ```ts
  const target = identifier.toLowerCase();
  const match = sessions.find((s) => sessionKey(s).toLowerCase() === target);
  ```
  
  The same issue exists in `resolveSession()` at line 56 for `BASE_SESSION` env var matching.

- [suggestion] packages/account-cli/src/core/paths.ts:4-8
  `getBaseDir()` re-checks `process.env.BASE_ACCOUNT_DIR` on every call, which silently overrides any value set by `setBaseDir()`. In unit tests this works only because the env var isn't set in the test process. If CI or a developer happens to set `BASE_ACCOUNT_DIR`, `setBaseDir()` would be silently ignored and tests would write to the wrong directory.
  
  Consider having `getBaseDir()` simply return `baseDir` without the env var fallback, since the env var is already read during module initialization:
  ```ts
  export function getBaseDir(): string {
    return baseDir;
  }
  ```

- [suggestion] packages/account-cli/src/core/paths.ts:31-37
  The path traversal guard uses a hardcoded forward slash (`${dir}/`), which won't work on Windows where `resolve()` produces backslash-separated paths. This means the guard would reject *all* identifiers on Windows.
  
  Use `path.sep` for cross-platform correctness:
  ```ts
  import { sep } from 'node:path';
  const normalizedDir = resolve(dir) + sep;
  if (!filePath.startsWith(normalizedDir)) {
    throw new Error('Invalid session identifier: path traversal detected');
  }
  ```

SHA 4f8657f9 · gemini-3.1-pro-preview → claude-opus-4-6

Comment on lines +12 to +15
if (size > 0) {
const fd = openSync(filePath, 'w');
writeSync(fd, randomBytes(size));
closeSync(fd);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Critical: 'w' flag truncates the file, defeating secure deletion.

Using openSync(filePath, 'w') truncates the file to 0 bytes before writing. The OS releases the original data blocks back to the free pool, and randomBytes(size) is written to newly allocated blocks. The original sensitive data remains on disk and is recoverable with forensic tools.

To overwrite the existing data blocks in-place, use the 'r+' flag:

const fd = openSync(filePath, 'r+');

Comment on lines +11 to +32
export function verifyDirectoryPermissions(dir: string): void {
if (!existsSync(dir)) return;
const stats = statSync(dir);
const mode = stats.mode & 0o777;
if (mode !== REQUIRED_DIR_MODE) {
throw new CLIError(
'INSECURE_PERMISSIONS',
`Directory ${dir} has mode ${mode.toString(8).padStart(3, '0')}, expected 700. ` +
`Fix with: chmod 700 "${dir}"`
);
}
}

/**
* Verify that a file has owner-only read/write permissions (0o600).
* Throws if the file is group- or world-readable.
*/
export function verifyFilePermissions(filePath: string): void {
if (!existsSync(filePath)) return;
const stats = statSync(filePath);
const mode = stats.mode & 0o777;
if (mode !== REQUIRED_FILE_MODE) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Critical: Strict POSIX permission checks will break the CLI on Windows.

Node.js on Windows does not map POSIX permissions correctly. statSync().mode & 0o777 will return values like 0o666 or 0o777 regardless of the actual Windows ACLs. The strict equality checks (mode !== 0o700 / mode !== 0o600) will always fail, permanently blocking Windows users with INSECURE_PERMISSIONS errors.

Bypass these checks on Windows:

if (process.platform === 'win32') return;

Comment on lines +109 to +114
appendAuditLog('session_destroy', mode, identifier);
}

export function destroyAllSessions(): string[] {
const sessions = listSessions();
const labels: string[] = [];
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destroyAllSessions depends on listSessions(), which throws on corrupted files or incorrect permissions. This means a single broken session file prevents destroy --all from working — defeating the user's escape hatch to reset state.

Instead, read the directory directly and delete matching filenames without parsing:

export function destroyAllSessions(): string[] {
  const dir = sessionsDir();
  if (!existsSync(dir)) return [];
  verifyDirectoryPermissions(dir);
  
  const labels: string[] = [];
  for (const f of readdirSync(dir)) {
    const match = SESSION_FILE_RE.exec(f);
    if (!match) continue;
    secureDelete(join(dir, f));
    appendAuditLog('session_destroy_all', match[1] as SessionMode, match[2]);
    labels.push(`${match[1]}:${match[2]}`);
  }
  return labels;
}

Comment on lines +95 to +99
const sessions = listSessions();
const match = sessions.find((s) => sessionKey(s) === identifier);
if (!match) {
throw new CLIError('INVALID_INPUT', `No session found for ${identifier}`);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ethereum addresses are case-insensitive, but this comparison uses strict ===. A user typing 0xeoa444 when the stored session has 0xEoa444 will get an INVALID_INPUT error.

Use case-insensitive comparison:

const target = identifier.toLowerCase();
const match = sessions.find((s) => sessionKey(s).toLowerCase() === target);

The same issue exists in resolveSession() at line 56 for BASE_SESSION env var matching.

Comment on lines +4 to +8
let baseDir = process.env.BASE_ACCOUNT_DIR || join(homedir(), '.base-account');

export function getBaseDir(): string {
return process.env.BASE_ACCOUNT_DIR || baseDir;
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getBaseDir() re-checks process.env.BASE_ACCOUNT_DIR on every call, which silently overrides any value set by setBaseDir(). In unit tests this works only because the env var isn't set in the test process. If CI or a developer happens to set BASE_ACCOUNT_DIR, setBaseDir() would be silently ignored and tests would write to the wrong directory.

Consider having getBaseDir() simply return baseDir without the env var fallback, since the env var is already read during module initialization:

export function getBaseDir(): string {
  return baseDir;
}

Comment on lines +31 to +37
export function sessionFile(mode: string, identifier: string): string {
const dir = sessionsDir();
const filePath = resolve(dir, `${mode}-${identifier}.json`);
if (!filePath.startsWith(`${dir}/`) && filePath !== dir) {
throw new Error(`Invalid session identifier: path traversal detected`);
}
return filePath;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path traversal guard uses a hardcoded forward slash (${dir}/), which won't work on Windows where resolve() produces backslash-separated paths. This means the guard would reject all identifiers on Windows.

Use path.sep for cross-platform correctness:

import { sep } from 'node:path';
const normalizedDir = resolve(dir) + sep;
if (!filePath.startsWith(normalizedDir)) {
  throw new Error('Invalid session identifier: path traversal detected');
}

- Fix secure deletion: use 'r+' flag instead of 'w' to overwrite
  existing data blocks in-place rather than truncating first
- Add Windows platform guard to permission checks so POSIX-only
  mode verification is skipped on win32
- Make destroyAllSessions read directory directly instead of going
  through listSessions(), so a single corrupt file can't block the
  escape hatch
- Use case-insensitive address comparison in resolveSession and
  session destroy for Ethereum address compatibility
- Remove redundant env var re-check from getBaseDir() that could
  silently override setBaseDir() in tests
- Use path.sep in path traversal guard for Windows compatibility

Made-with: Cursor
@fan-zhang-sv
Copy link
Copy Markdown
Collaborator Author

@spencerstock ty! just addressed the comments!

Copy link
Copy Markdown
Collaborator

@montycheese montycheese left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how are you testing session creation currently?

export type ExternalEoaSession = {
version: typeof SESSION_VERSION;
mode: 'external-eoa';
account: `0x${string}`;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not necessarily true for a caip address

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good call! updated!

export type SmartWalletSession = {
version: typeof SESSION_VERSION;
mode: 'smart-wallet';
account: `0x${string}`;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not necessarily true for a caip address

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

V1 smart wallet session would be EVM only, so think we can keep this one

@@ -0,0 +1,54 @@
import type { ChainId } from './caip.js';

export const SESSION_VERSION = 1;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great idea.

session
.command('destroy')
.description('Delete a session')
.argument('[identifier]', 'Sub-account, EOA, or account address of session to destroy')
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sub-account, EOA, or account address of session to destroy

is a bit confusing to read here.

maybe something like "the session's wallet address to destroy"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated!

const destroyed = destroyAllSessions();
formatOutput({ status: 'destroyed', sessions: destroyed }, globalOpts.json);
} else if (identifier && opts.mode) {
const mode = opts.mode as SessionMode;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whats the purpose of passing a mode? whats the expected use case

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this mode is being used to display to user that what session got deleted. currently this is the only human readable identifier, but im also not sure if we'd like to expose internal var to users, open to ideas

@fan-zhang-sv
Copy link
Copy Markdown
Collaborator Author

@montycheese i asked agents to help generate dummy session file in my disk for testing, i have some example in the integration test file

@fan-zhang-sv fan-zhang-sv merged commit 4e5882f into master Mar 25, 2026
9 checks passed
@fan-zhang-sv fan-zhang-sv deleted the felix/cli-session-management branch March 25, 2026 20:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants