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
31 changes: 2 additions & 29 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @spool-lab/cli

Command-line interface for [Spool](https://spool.pro) — search your AI sessions and manage connector plugins from the terminal.
Command-line interface for [Spool](https://spool.pro) — search your AI sessions from the terminal.

## Install

Expand Down Expand Up @@ -37,37 +37,10 @@ spool sync # Index new AI sessions (Claude, Codex, Gemini)
spool sync --watch # Keep watching for new sessions
```

### Connector management

```bash
spool connector list # List installed connectors
spool connector list --json # Output as JSON
spool connector install <package> # Install from npm
spool connector install @spool-lab/connector-github -y # Skip confirmation
spool connector uninstall <id> # Remove a connector + data
spool connector status <id> # Detailed sync state + auth check
spool connector sync <id> # Run sync manually
spool connector sync <id> --reset # Clear data and resync
spool connector update # Check all for npm updates
spool connector update --apply # Apply available updates
```

## Available connectors

Browse the full list at [spool.pro/connectors](https://spool.pro/connectors). Some examples:

```bash
spool connector install @spool-lab/connector-github
spool connector install @spool-lab/connector-twitter-bookmarks
spool connector install @spool-lab/connector-reddit
spool connector install @graydawnc/connector-youtube
```

## Data location

All data is stored locally in `~/.spool/`:
- `spool.db` — SQLite database with sessions, messages, and captures
- `connectors/` — installed connector plugins
- `spool.db` — SQLite database with sessions and messages

## License

Expand Down
152 changes: 1 addition & 151 deletions packages/cli/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ const SCHEMA_SQL = `
);
INSERT INTO sources (name, base_path) VALUES
('claude','~/.claude/projects'),('codex','~/.codex/sessions'),
('gemini','~/.gemini/tmp'),('connector','<plugin>');
('gemini','~/.gemini/tmp');

CREATE TABLE projects (
id INTEGER PRIMARY KEY, source_id INTEGER NOT NULL REFERENCES sources(id),
Expand Down Expand Up @@ -163,38 +163,6 @@ const SCHEMA_SQL = `
content='session_search', content_rowid='session_id', tokenize='trigram'
);

CREATE TABLE captures (
id INTEGER PRIMARY KEY, source_id INTEGER NOT NULL REFERENCES sources(id),
capture_uuid TEXT NOT NULL UNIQUE, url TEXT NOT NULL,
title TEXT NOT NULL DEFAULT '', content_text TEXT NOT NULL DEFAULT '',
author TEXT, platform TEXT NOT NULL, platform_id TEXT,
content_type TEXT NOT NULL DEFAULT 'page', thumbnail_url TEXT,
metadata TEXT NOT NULL DEFAULT '{}', captured_at TEXT NOT NULL,
indexed_at TEXT NOT NULL DEFAULT (datetime('now')), raw_json TEXT
);
CREATE VIRTUAL TABLE captures_fts USING fts5(
title, content_text, content='captures', content_rowid='id',
tokenize='unicode61 remove_diacritics 1'
);
CREATE VIRTUAL TABLE captures_fts_trigram USING fts5(
title, content_text, content='captures', content_rowid='id', tokenize='trigram'
);

CREATE TABLE capture_connectors (
capture_id INTEGER NOT NULL REFERENCES captures(id) ON DELETE CASCADE,
connector_id TEXT NOT NULL, PRIMARY KEY (capture_id, connector_id)
);
CREATE INDEX idx_capture_connectors_connector ON capture_connectors(connector_id);

CREATE TABLE connector_sync_state (
connector_id TEXT PRIMARY KEY, head_cursor TEXT, head_item_id TEXT,
tail_cursor TEXT, tail_complete INTEGER NOT NULL DEFAULT 0,
last_forward_sync_at TEXT, last_backfill_sync_at TEXT,
total_synced INTEGER NOT NULL DEFAULT 0, consecutive_errors INTEGER NOT NULL DEFAULT 0,
enabled INTEGER NOT NULL DEFAULT 1, config_json TEXT NOT NULL DEFAULT '{}',
last_error_at TEXT, last_error_code TEXT, last_error_message TEXT
);

CREATE TRIGGER messages_fts_insert AFTER INSERT ON messages BEGIN
INSERT INTO messages_fts(rowid, content_text) VALUES(NEW.id, NEW.content_text);
INSERT INTO messages_fts_trigram(rowid, content_text) VALUES(NEW.id, NEW.content_text);
Expand Down Expand Up @@ -381,121 +349,3 @@ describe('sync', () => {
}
})
})

describe('connector install', () => {
it('exits with error when package arg is missing', () => {
const out = runFail(['connector', 'install'])
expect(out).toContain("missing required argument")
})

it('fails gracefully on nonexistent package', () => {
const dir = mkdtempSync(join(tmpdir(), 'spool-cli-install-'))
try {
const out = runFail(['connector', 'install', '@spool-lab/nonexistent-pkg-test', '-y'], { SPOOL_DATA_DIR: dir })
expect(out).toContain('Failed')
} finally {
rmSync(dir, { recursive: true, force: true })
}
})
})

describe('connector sync', () => {
it('lists connectors or reports none when no arg given', () => {
const dir = mkdtempSync(join(tmpdir(), 'spool-cli-csync-'))
try {
let out: string
try {
out = run(['connector', 'sync'], { SPOOL_DATA_DIR: dir })
} catch {
out = runFail(['connector', 'sync'], { SPOOL_DATA_DIR: dir })
}
expect(out).toMatch(/Available connectors|No connectors installed/)
} finally {
rmSync(dir, { recursive: true, force: true })
}
})

it('exits with error for unknown connector', () => {
const dir = mkdtempSync(join(tmpdir(), 'spool-cli-csync-'))
try {
const out = runFail(['connector', 'sync', 'nonexistent-connector'], { SPOOL_DATA_DIR: dir })
expect(out).toContain('Unknown connector')
} finally {
rmSync(dir, { recursive: true, force: true })
}
})
})

describe('connector list', () => {
it('lists connectors or reports none', () => {
const dir = mkdtempSync(join(tmpdir(), 'spool-cli-clist-'))
try {
const out = run(['connector', 'list'], { SPOOL_DATA_DIR: dir })
expect(out).toMatch(/items|No connectors installed/)
} finally {
rmSync(dir, { recursive: true, force: true })
}
})

it('outputs JSON with --json', () => {
const dir = mkdtempSync(join(tmpdir(), 'spool-cli-clist-'))
try {
const out = run(['connector', 'list', '--json'], { SPOOL_DATA_DIR: dir })
const parsed = JSON.parse(out)
expect(Array.isArray(parsed)).toBe(true)
} finally {
rmSync(dir, { recursive: true, force: true })
}
})
})

describe('connector status', () => {
it('exits with error for unknown connector', () => {
const dir = mkdtempSync(join(tmpdir(), 'spool-cli-cstatus-'))
try {
const out = runFail(['connector', 'status', 'nonexistent-connector'], { SPOOL_DATA_DIR: dir })
expect(out).toContain('Unknown connector')
} finally {
rmSync(dir, { recursive: true, force: true })
}
})

it('exits with error when id is missing', () => {
const out = runFail(['connector', 'status'])
expect(out).toContain("missing required argument")
})
})

describe('connector uninstall', () => {
it('exits with error for unknown connector', () => {
const dir = mkdtempSync(join(tmpdir(), 'spool-cli-cuninstall-'))
try {
const out = runFail(['connector', 'uninstall', 'nonexistent-connector', '-y'], { SPOOL_DATA_DIR: dir })
expect(out).toContain('Unknown connector')
} finally {
rmSync(dir, { recursive: true, force: true })
}
})
})

describe('connector update', () => {
it('checks for updates without error', () => {
const dir = mkdtempSync(join(tmpdir(), 'spool-cli-cupdate-'))
try {
const out = run(['connector', 'update'], { SPOOL_DATA_DIR: dir })
expect(out).toMatch(/up to date|No connectors to check|→/)
} finally {
rmSync(dir, { recursive: true, force: true })
}
})

it('exits with error for unknown connector', () => {
const dir = mkdtempSync(join(tmpdir(), 'spool-cli-cupdate-'))
try {
const out = runFail(['connector', 'update', 'nonexistent-connector'], { SPOOL_DATA_DIR: dir })
expect(out).toContain('Unknown connector')
} finally {
rmSync(dir, { recursive: true, force: true })
}
})
})
106 changes: 0 additions & 106 deletions packages/cli/src/commands/connector-shared.ts

This file was deleted.

Loading