Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 6 additions & 17 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,19 @@
# Repository Guidelines

## Project Structure & Module Organization
- `src/` holds all TypeScript. `src/cli.tsx` is the entry point, `src/App.tsx` routes Ink commands, `src/components/` hosts reusable views, and `src/keyset/` manages key material flows. Keep helpers near their callers; split files past ~150 lines into focused modules under `components/` or `keyset/`.
- Place specs alongside code (`feature.test.ts`) or under `src/__tests__/`. Generated bundles live in `dist/`, rebuilt by `npm run build`; never edit compiled output.
- Prompts and automation cues live in `llm/`; update them whenever UX or protocol semantics change so downstream agents stay aligned.
The CLI lives in `src/cli.tsx`, which bootstraps Ink rendering. Routes and screen wiring belong in `src/App.tsx`, while reusable UI sits under `src/components/`. Key exchange and credential helpers stay in `src/keyset/`. Keep feature-specific helpers close to their caller; split files that exceed ~150 lines into focused modules. Tests may live beside the implementation as `feature.test.ts` or under `src/__tests__/`. Generated bundles in `dist/` are read-only artifacts. Protocol prompts and agent scripts live in `llm/`; keep them updated whenever UX flows change so downstream tooling stays consistent.

## Build, Test, and Development Commands
- `npm run dev` — hot-reloads the CLI via `tsx`, ideal while iterating on Ink screens.
- `npm run build` — invokes `tsup` on `src/cli.tsx` and emits `dist/cli.js` with the shipping shebang.
- `npm run start` — executes the compiled CLI exactly as users receive it; tack on flags like `npm run start -- --help` for smoke checks.
- `npm run typecheck` / `npm test` — run the TypeScript compiler with `--noEmit`; treat failures as release blockers.
Use `npm run dev` for hot-reloading during Ink development. Run `npm run build` to compile the distributable CLI via `tsup` into `dist/cli.js`. Smoke-test the bundled build with `npm run start -- --help` or alternate flags. Type safety and lints run through `npm run typecheck`, and `npm test` executes the project’s test suite; treat any failure as a release blocker.

## Coding Style & Naming Conventions
- Stick to TypeScript + ESM, 2-space indentation, single quotes, trailing commas, and imports sorted shallow-to-deep.
- Name components `PascalCase`, utilities `camelCase`, constants `SCREAMING_SNAKE_CASE`, and CLI flags lowercase (e.g., `--verbose`).
- Add concise comments only when intent is non-obvious—serialization boundaries, key lifecycles, or tricky Ink flows.
We target Node.js 18+, TypeScript + ESM modules, 2-space indentation, single quotes, trailing commas, and imports ordered shallow-to-deep. Components adopt `PascalCase`, utilities use `camelCase`, constants prefer `SCREAMING_SNAKE_CASE`, and CLI flags remain lower-case (e.g., `--verbose`). Document non-obvious flows with concise comments, especially around key lifecycle management.

## Testing Guidelines
- Lean on type safety first; add `node:test` or `vitest` suites for branching logic. Name files `feature.test.ts` and colocate when feasible.
- Document manual checks (commands, sample args) in PRs so reviewers can replay the scenario quickly.
Favor type coverage first; add `node:test` or `vitest` specs when logic branches or data transforms appear. Test files follow the `feature.test.ts` pattern and should exercise both happy paths and failure modes. Run the full suite with `npm test` before opening a PR and capture any manual validation steps in the PR description for replayability.

## Commit & Pull Request Guidelines
- Commit subjects stay imperative, under 72 characters (`Add passphrase prompt`), and focus on a single concern. Reference issues in the body when helpful.
- PRs summarize user impact, link tracking issues, and attach screenshots or terminal recordings for UX shifts.
- Call out edits to `llm/` or cryptographic paths so reviewers can validate downstream implications.
Write imperative, single-purpose commit subjects under 72 characters (e.g., `Add passphrase prompt`) and add contextual detail in the body if necessary. PRs must summarize user impact, reference tracking issues, and attach terminal recordings or screenshots for UX updates. Call out edits to `llm/` or cryptographic logic so reviewers prioritize a second pass.

## Security & Configuration Tips
- Use Node.js 18+ (`nvm use 18`) before installing dependencies. Never commit production secrets; treat `tmp-shares/` and similar artifacts as disposable.
- Remove temporary key files and redact sample payloads before pushing branches or publishing packages.
Install dependencies under Node.js 18 via `nvm use 18`. Never commit secrets or temporary key material; treat `tmp-shares/` as disposable. Remove any generated keys and redact sensitive payloads before pushing or publishing.
8 changes: 6 additions & 2 deletions llm/context/igloo-core-readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,17 +314,20 @@ try {
}
```

#### `sendEcho(groupCredential, shareCredential, options?)`
#### `sendEcho(groupCredential, shareCredential, challenge, options?)`

Send an echo signal to notify other devices that a share has been imported.

```typescript
import { sendEcho } from '@frostr/igloo-core';
import { randomBytes } from 'crypto';

try {
const challenge = randomBytes(32).toString('hex'); // 32-byte (64 hex char) challenge
const sent = await sendEcho(
groupCredential,
shareCredential,
challenge,
{
relays: ['wss://relay.damus.io'],
timeout: 10000
Expand All @@ -336,6 +339,7 @@ try {
}
```

`challenge` must be an even-length hexadecimal string (32 bytes / 64 hex characters recommended).
#### `startListeningForAllEchoes(groupCredential, shareCredentials, callback, options?)`

Starts listening for echo events on all shares in a keyset.
Expand Down Expand Up @@ -1000,7 +1004,7 @@ export function setupNodeEvents(node: BifrostNode, config: NodeEventConfig): voi
// Echo functions
export function awaitShareEcho(groupCredential: string, shareCredential: string, options?: EchoOptions): Promise<boolean>
export function startListeningForAllEchoes(groupCredential: string, shareCredentials: string[], callback: EchoReceivedCallback, options?: EchoOptions): EchoListener
export function sendEcho(groupCredential: string, shareCredential: string, options?: EchoOptions): Promise<boolean>
export function sendEcho(groupCredential: string, shareCredential: string, challenge: string, options?: EchoOptions): Promise<boolean>
export const DEFAULT_ECHO_RELAYS: string[]

// Nostr functions
Expand Down
11 changes: 6 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"homepage": "https://github.com/FROSTR-ORG/igloo-cli#readme",
"dependencies": {
"@frostr/bifrost": "^1.0.7",
"@frostr/igloo-core": "^0.2.0",
"@frostr/igloo-core": "^0.2.1",
"@noble/ciphers": "^2.0.1",
"@noble/curves": "^2.0.1",
"@noble/hashes": "^2.0.1",
Expand Down
21 changes: 21 additions & 0 deletions src/components/keyset/KeysetLoad.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {Box, Text} from 'ink';
import {decodeGroup, decodeShare} from '@frostr/igloo-core';
import {readShareFiles, decryptShareCredential, ShareMetadata} from '../../keyset/index.js';
import {Prompt} from '../ui/Prompt.js';
import {useShareEchoListener} from './useShareEchoListener.js';

type KeysetLoadProps = {
args: string[];
Expand Down Expand Up @@ -59,6 +60,13 @@ export function KeysetLoad({args}: KeysetLoadProps) {
}
}, [attemptPreselect, selectedShare]);

const decryptedShare = result?.share ?? null;
const decryptedGroup = result?.group ?? null;
const {status: echoStatus, message: echoMessage} = useShareEchoListener(
decryptedGroup,
decryptedShare
);

if (state.loading) {
return (
<Box flexDirection="column">
Expand Down Expand Up @@ -211,6 +219,19 @@ export function KeysetLoad({args}: KeysetLoadProps) {
) : null}
</Box>
) : null}
<Box marginTop={1} flexDirection="column">
{echoStatus === 'listening' ? (
<Box flexDirection="column">
<Text color="cyan">
Waiting for echo confirmation{shareIndex !== undefined ? ` on share ${shareIndex}` : ''}…
</Text>
{echoMessage ? <Text color="yellow">{echoMessage}</Text> : null}
</Box>
) : null}
{echoStatus === 'success' ? (
<Text color="green">Echo confirmed by the receiving device.</Text>
) : null}
</Box>
</Box>
);
}
54 changes: 53 additions & 1 deletion src/components/keyset/ShareSaver.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
createDefaultPolicy
} from '../../keyset/index.js';
import {Prompt} from '../ui/Prompt.js';
import {useShareEchoListener} from './useShareEchoListener.js';

type ShareSaverProps = {
keysetName: string;
Expand Down Expand Up @@ -73,6 +74,25 @@ export function ShareSaver({
const share = shares[currentIndex];
const isAutomated = typeof autoPassword === 'string' && autoPassword.length > 0;

const {status: echoStatus, message: echoMessage} = useShareEchoListener(
groupCredential,
share?.credential
);

const shareCredentialBlock = (
<Box marginTop={1} flexDirection="column">
<Text color="cyanBright">Share credential</Text>
<Text color="white">{share?.credential ?? 'unknown'}</Text>
</Box>
);

const groupCredentialBlock = (
<Box marginTop={1} flexDirection="column">
<Text color="magentaBright">Group credential</Text>
<Text color="white">{groupCredential}</Text>
</Box>
);

const summaryView = (
<Box flexDirection="column">
<Text color="cyan">All shares processed.</Text>
Expand Down Expand Up @@ -191,6 +211,30 @@ export function ShareSaver({
return undefined;
}

function renderEchoStatus() {
if (!share) {
return null;
}
if (echoStatus === 'listening') {
return (
<Box flexDirection="column">
<Text color="cyan">
Waiting for echo confirmation on share {share.index}…
</Text>
{echoMessage ? <Text color="yellow">{echoMessage}</Text> : null}
</Box>
);
}
if (echoStatus === 'success') {
return (
<Box flexDirection="column" marginTop={1} marginBottom={1}>
<Text color="green" bold>✓ Echo confirmed! Receiving device got the share.</Text>
</Box>
);
}
return null;
}

if (isAutomated) {
if (autoState === 'idle') {
if (!autoPassword || autoPassword.length < 8) {
Expand Down Expand Up @@ -270,6 +314,9 @@ export function ShareSaver({
return (
<Box flexDirection="column">
<Text color="cyan">Encrypting share {share.index}…</Text>
{shareCredentialBlock}
{groupCredentialBlock}
{renderEchoStatus()}
</Box>
);
}
Expand All @@ -279,6 +326,9 @@ export function ShareSaver({
<Box flexDirection="column">
<Text color="green">Share {share.index} saved.</Text>
{feedback ? <Text color="gray">{feedback}</Text> : null}
{shareCredentialBlock}
{groupCredentialBlock}
{renderEchoStatus()}
<Prompt
key={`continue-${share.index}`}
label="Press Enter to continue"
Expand All @@ -295,7 +345,9 @@ export function ShareSaver({
return (
<Box flexDirection="column">
<Text color="cyan">Share {share.index} of {shareCredentials.length}</Text>
<Text color="gray">{share.credential}</Text>
{shareCredentialBlock}
{groupCredentialBlock}
{renderEchoStatus()}
<Text>
Set a password to encrypt this share. Leave blank to skip saving and handle it manually.
</Text>
Expand Down
Loading