Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

cloudctx

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cloudctx - npm Package Compare versions

Comparing version
0.1.3
to
0.1.4
+101
lib/config.js
import { readFileSync, writeFileSync, existsSync } from 'fs';
import { join } from 'path';
import { getDataDir, ensureDataDir } from './db.js';
const CONFIG_PATH = join(getDataDir(), 'config.json');
const DEFAULTS = {
statusline: false,
statusline_color: 'cyan',
};
const KNOWN_KEYS = Object.keys(DEFAULTS);
const KEY_DESCRIPTIONS = {
statusline: 'Show saved thread name in Claude Code status line',
statusline_color: 'Color for statusline text (see: cloudctx config colors)',
};
const BOOL_KEYS = new Set(['statusline']);
const STRING_KEYS = new Set(['statusline_color']);
export const STATUSLINE_COLORS = {
default: '',
black: '30',
red: '31',
green: '32',
yellow: '33',
blue: '34',
magenta: '35',
cyan: '36',
white: '37',
bright_black: '90',
bright_red: '91',
bright_green: '92',
bright_yellow: '93',
bright_blue: '94',
bright_magenta: '95',
bright_cyan: '96',
bright_white: '97',
};
export function isBoolKey(key) { return BOOL_KEYS.has(key); }
export function isStringKey(key) { return STRING_KEYS.has(key); }
export function getConfigPath() {
return CONFIG_PATH;
}
function readRaw() {
if (!existsSync(CONFIG_PATH)) return {};
try {
return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
} catch {
return {};
}
}
export function getConfig() {
return { ...DEFAULTS, ...readRaw() };
}
export function getConfigValue(key) {
return getConfig()[key];
}
export function setConfig(key, value) {
ensureDataDir();
const current = readRaw();
current[key] = value;
writeFileSync(CONFIG_PATH, JSON.stringify(current, null, 2) + '\n');
}
export function unsetConfig(key) {
if (!existsSync(CONFIG_PATH)) return;
const current = readRaw();
delete current[key];
writeFileSync(CONFIG_PATH, JSON.stringify(current, null, 2) + '\n');
}
export function parseBool(val) {
const v = String(val).toLowerCase().trim();
if (['true', 'on', '1', 'yes', 'enable', 'enabled'].includes(v)) return true;
if (['false', 'off', '0', 'no', 'disable', 'disabled'].includes(v)) return false;
return null;
}
export function isKnownKey(key) {
return KNOWN_KEYS.includes(key);
}
export function listKnownKeys() {
return [...KNOWN_KEYS];
}
export function describeKey(key) {
return KEY_DESCRIPTIONS[key] || '';
}
export function getDefaults() {
return { ...DEFAULTS };
}
import { readFileSync } from 'fs';
import { getReadonlyDb, dbExists } from './db.js';
import { getConfigValue, STATUSLINE_COLORS } from './config.js';
export function runStatusline() {
try {
if (!dbExists()) {
process.exit(0);
}
let input = '';
try {
input = readFileSync('/dev/stdin', 'utf-8');
} catch {}
let sessionId = '';
try {
const parsed = JSON.parse(input);
sessionId = parsed.session_id || parsed.sessionId || '';
} catch {}
if (!sessionId) process.exit(0);
const db = getReadonlyDb();
const row = db.prepare(
'SELECT name FROM saved_threads WHERE session_id = ? ORDER BY saved_at DESC LIMIT 1'
).get(sessionId);
db.close();
if (row && row.name) {
const colorName = getConfigValue('statusline_color') || 'cyan';
const code = STATUSLINE_COLORS[colorName] ?? '';
const prefix = code ? `\x1b[1;${code}m` : `\x1b[1m`;
process.stdout.write(`${prefix}📌 ${row.name}\x1b[0m`);
}
} catch {
// silent — statusline should never crash CC
}
process.exit(0);
}
+208
-1

@@ -6,5 +6,7 @@ #!/usr/bin/env node

import { runHook } from '../lib/hook.js';
import { installHook, uninstallHook, installClaudeMd, uninstallClaudeMd, installSlashCommand, uninstallSlashCommand } from '../lib/install.js';
import { runStatusline } from '../lib/statusline.js';
import { installHook, uninstallHook, installClaudeMd, uninstallClaudeMd, installSlashCommand, uninstallSlashCommand, installStatusline, uninstallStatusline } from '../lib/install.js';
import { saveThread, removeThread, renameThread, listThreads, interactiveLaunch } from '../lib/launch.js';
import { ingestDoc, listDocs, searchDocs, deleteDoc } from '../lib/docs.js';
import { getConfig, getConfigValue, setConfig, unsetConfig, parseBool, isKnownKey, listKnownKeys, describeKey, getConfigPath, isBoolKey, isStringKey, STATUSLINE_COLORS } from '../lib/config.js';
import { existsSync, rmSync, statSync } from 'fs';

@@ -28,2 +30,10 @@

case 'statusline':
runStatusline();
break;
case 'config':
await cmdConfig(args.slice(1));
break;
case 'sync':

@@ -300,2 +310,7 @@ case 'seed':

// Remove statusline if it was installed
if (uninstallStatusline()) {
console.log(' ✓ Status line removed');
}
// Delete data dir

@@ -385,2 +400,187 @@ if (existsSync(getDataDir())) {

async function cmdConfig(subArgs) {
const sub = subArgs[0] || 'list';
if (sub === 'list') {
const config = getConfig();
console.log('');
console.log(' CloudCtx Config');
console.log(' ' + '─'.repeat(50));
for (const key of listKnownKeys()) {
const val = config[key];
const desc = describeKey(key);
console.log(` ${key.padEnd(18)} ${String(val).padEnd(6)} ${desc}`);
}
console.log('');
console.log(` File: ${getConfigPath()}`);
console.log('');
console.log(' cloudctx config set <key> <true|false>');
console.log(' cloudctx config unset <key>');
console.log('');
return;
}
if (sub === 'get') {
const key = subArgs[1];
if (!key) { console.error('Usage: cloudctx config get <key>'); process.exit(1); }
if (!isKnownKey(key)) {
console.error(`Unknown key: ${key}`);
console.error(`Known keys: ${listKnownKeys().join(', ')}`);
process.exit(1);
}
console.log(getConfigValue(key));
return;
}
if (sub === 'color' || sub === 'colors') {
if (!process.stdin.isTTY) {
console.log('');
console.log(' Valid statusline_color values:');
for (const name of Object.keys(STATUSLINE_COLORS)) {
const code = STATUSLINE_COLORS[name];
const preview = code ? `\x1b[1;${code}m📌 ${name}\x1b[0m` : `\x1b[1m📌 ${name}\x1b[0m`;
console.log(` ${preview}`);
}
console.log('');
console.log(' cloudctx config set statusline_color <name>');
console.log('');
return;
}
const chosen = await pickColorInteractive();
if (chosen) {
setConfig('statusline_color', chosen);
const code = STATUSLINE_COLORS[chosen];
const preview = code ? `\x1b[1;${code}m📌 ${chosen}\x1b[0m` : `\x1b[1m📌 ${chosen}\x1b[0m`;
console.log(` ✓ statusline_color = ${preview}`);
} else {
console.log(' (cancelled)');
}
return;
}
if (sub === 'set') {
const key = subArgs[1];
const value = subArgs[2];
if (!key || value === undefined) {
console.error('Usage: cloudctx config set <key> <value>');
process.exit(1);
}
if (!isKnownKey(key)) {
console.error(`Unknown key: ${key}`);
console.error(`Known keys: ${listKnownKeys().join(', ')}`);
process.exit(1);
}
let storeValue;
if (isBoolKey(key)) {
const bool = parseBool(value);
if (bool === null) {
console.error(`Value must be true/false (or on/off, yes/no) — got: ${value}`);
process.exit(1);
}
storeValue = bool;
} else if (isStringKey(key)) {
if (key === 'statusline_color' && !(value in STATUSLINE_COLORS)) {
console.error(`Unknown color: ${value}`);
console.error(`Run: cloudctx config colors`);
process.exit(1);
}
storeValue = value;
} else {
storeValue = value;
}
setConfig(key, storeValue);
const bool = storeValue;
if (key === 'statusline') {
if (bool) {
installStatusline();
console.log(` ✓ statusline = true — wired into ~/.claude/settings.json`);
console.log(` Open a new Claude Code session to see it.`);
} else {
uninstallStatusline();
console.log(` ✓ statusline = false — removed from ~/.claude/settings.json`);
}
} else {
console.log(` ✓ ${key} = ${storeValue}`);
}
return;
}
if (sub === 'unset') {
const key = subArgs[1];
if (!key) { console.error('Usage: cloudctx config unset <key>'); process.exit(1); }
unsetConfig(key);
if (key === 'statusline') uninstallStatusline();
console.log(` ✓ unset ${key}`);
return;
}
console.error('Usage: cloudctx config [list|get|set|unset] ...');
process.exit(1);
}
async function pickColorInteractive() {
const colors = Object.keys(STATUSLINE_COLORS);
const currentColor = getConfigValue('statusline_color') || 'cyan';
let cursor = Math.max(0, colors.indexOf(currentColor));
const render = () => {
process.stdout.write('\x1b[2J\x1b[H');
console.log('');
console.log(' \x1b[1mCloudCtx — Choose statusline color\x1b[0m');
console.log(' \x1b[2m↑↓ navigate ⏎ save q cancel\x1b[0m');
console.log('');
for (let i = 0; i < colors.length; i++) {
const name = colors[i];
const code = STATUSLINE_COLORS[name];
const preview = code ? `\x1b[1;${code}m📌 ${name}\x1b[0m` : `\x1b[1m📌 ${name}\x1b[0m`;
const pointer = i === cursor ? ' \x1b[36m❯\x1b[0m ' : ' ';
const mark = name === currentColor ? ' \x1b[2m(current)\x1b[0m' : '';
console.log(`${pointer}${preview}${mark}`);
}
console.log('');
};
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.setEncoding('utf-8');
render();
return new Promise((resolve) => {
const cleanup = () => {
process.stdout.write('\x1b[2J\x1b[H');
process.stdin.setRawMode(false);
process.stdin.removeListener('data', onKey);
process.stdin.pause();
};
const onKey = (key) => {
if (key === '\x03' || key === 'q' || key === '\x1b') {
cleanup();
resolve(null);
return;
}
if (key === '\x1b[A' || key === 'k') {
cursor = (cursor - 1 + colors.length) % colors.length;
render();
return;
}
if (key === '\x1b[B' || key === 'j') {
cursor = (cursor + 1) % colors.length;
render();
return;
}
if (key === '\r' || key === '\n') {
cleanup();
resolve(colors[cursor]);
return;
}
};
process.stdin.on('data', onKey);
});
}
function cmdImport(dbPath) {

@@ -473,5 +673,12 @@ if (!dbPath) {

config List all config values
config color Interactive color picker for statusline
config get <key> Get one value
config set <key> <value> Set a value (known keys: statusline, statusline_color)
config unset <key> Remove a value
hook (internal) UserPromptSubmit handler
statusline (internal) Claude Code statusLine handler
help Show this help
`);
}

@@ -96,2 +96,27 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync } from 'fs';

export function installStatusline() {
const binPath = getCloudctxBinPath();
let settings = {};
if (existsSync(SETTINGS_FILE)) {
try { settings = JSON.parse(readFileSync(SETTINGS_FILE, 'utf-8')); } catch {}
}
settings.statusLine = {
type: 'command',
command: `${binPath} statusline`,
};
writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + '\n');
return true;
}
export function uninstallStatusline() {
if (!existsSync(SETTINGS_FILE)) return false;
let settings;
try { settings = JSON.parse(readFileSync(SETTINGS_FILE, 'utf-8')); } catch { return false; }
const cmd = settings.statusLine?.command || '';
if (!cmd.includes('cloudctx') || !cmd.includes('statusline')) return false;
delete settings.statusLine;
writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + '\n');
return true;
}
export function uninstallHook() {

@@ -98,0 +123,0 @@ if (!existsSync(SETTINGS_FILE)) return false;

+1
-1
{
"name": "cloudctx",
"version": "0.1.3",
"version": "0.1.4",
"description": "Persistent memory for Claude Code. One command, full recall.",

@@ -5,0 +5,0 @@ "type": "module",