1 Commits

Author SHA1 Message Date
Alexander Whitestone
9dc47845cf feat: enhance aw-post CLI — --from-x, --file, --help flags (#219)
- --from-x "text": port X/Twitter posts (title from first 60 chars)
- --file path.md: post from markdown file (title from # header)
- --help: usage info
- Refactored into functions for reuse
- All modes auto-rebuild the blog after posting

Closes #219
2026-03-18 21:45:50 -04:00
3 changed files with 10 additions and 237 deletions

View File

@@ -1,55 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Lost in the Tower</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #0a0a0f;
color: #e0d8c8;
font-family: Georgia, 'Times New Roman', serif;
}
h1 {
font-size: 1.4rem;
font-weight: normal;
letter-spacing: 0.15em;
text-transform: uppercase;
margin-bottom: 1.5rem;
color: #8a7f6a;
}
p {
font-size: 0.9rem;
color: #6a6050;
margin-bottom: 2rem;
}
a {
color: #8a7f6a;
text-decoration: none;
padding: 0.8rem 2rem;
border: 1px solid #2a2520;
transition: border-color 0.3s, color 0.3s;
}
a:hover {
border-color: #8a7f6a;
color: #fff;
}
</style>
</head>
<body>
<h1>Lost in the Tower</h1>
<p>This room doesn't exist.</p>
<a href="/">Return to the Entry Hall</a>
</body>
</html>

View File

@@ -19,57 +19,8 @@
color: #6a6050;
}
.placeholder h1 { font-size: 1.2rem; font-weight: normal; margin-bottom: 1rem; }
.placeholder p { font-size: 0.85rem; margin-bottom: 0.5rem; }
.placeholder p { font-size: 0.85rem; }
.placeholder a { color: #8a7f6a; }
/* Connection status HUD */
#status-hud {
position: fixed;
top: 12px;
right: 12px;
background: rgba(10, 10, 15, 0.85);
border: 1px solid #2a2520;
border-radius: 6px;
padding: 8px 14px;
font-family: 'Courier New', monospace;
font-size: 0.75rem;
color: #6a6050;
z-index: 100;
min-width: 180px;
}
#status-hud .status-line {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 4px;
}
#status-hud .status-line:last-child { margin-bottom: 0; }
#status-hud .dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
#status-hud .dot.connecting { background: #b8860b; animation: pulse 1.2s ease-in-out infinite; }
#status-hud .dot.online { background: #4a9; }
#status-hud .dot.offline { background: #a44; }
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
#retry-btn {
display: none;
margin-top: 6px;
padding: 3px 10px;
background: #2a2520;
border: 1px solid #4a4030;
border-radius: 3px;
color: #8a7f6a;
font-family: 'Courier New', monospace;
font-size: 0.7rem;
cursor: pointer;
}
#retry-btn:hover { background: #3a3530; color: #c0b8a8; }
</style>
</head>
<body>
@@ -80,26 +31,7 @@
<p><a href="/">← Back to the Tower</a></p>
</div>
</div>
<div id="status-hud">
<div class="status-line">
<span class="dot connecting" id="status-dot"></span>
<span id="status-text">INITIALIZING</span>
</div>
<div class="status-line">AGENTS: <span id="agent-count">0</span></div>
<button id="retry-btn">Retry connection</button>
</div>
<!-- Reject unknown sub-paths: only /world/ is valid -->
<script>
(function() {
var path = window.location.pathname.replace(/\/+$/, '') || '/';
if (path !== '/world') {
window.location.replace('/404.html');
}
})();
</script>
<!-- Three.js scene will mount to #scene -->
<script type="module" src="main.js"></script>
<!-- <script type="module" src="main.js"></script> -->
</body>
</html>

View File

@@ -1,124 +1,20 @@
/**
* The Workshop — Three.js scene bootstrap
*
* Initializes the 3D world where Timmy lives.
* Handles WebSocket connection to tower-hermes backend with
* timeout, retry, and clear status display.
* This file will initialize the 3D world where Timmy lives.
* Currently a placeholder until tech decisions are made:
* - 3D engine confirmed (Three.js vs Babylon.js)
* - Character design direction chosen
* - WebSocket bridge to Timmy's soul designed (#243)
*
* See: #242 (3D world), #243 (WebSocket bridge), #265 (presence schema)
*/
// Future: import * as THREE from 'three';
const HERMES_WS_URL = (location.protocol === 'https:' ? 'wss://' : 'ws://') +
location.host + '/ws/tower';
const CONNECT_TIMEOUT_MS = 5000;
const RETRY_DELAY_MS = 3000;
const MAX_AUTO_RETRIES = 3;
const Status = { CONNECTING: 'connecting', ONLINE: 'online', OFFLINE: 'offline' };
const dom = {
dot: document.getElementById('status-dot'),
text: document.getElementById('status-text'),
agents: document.getElementById('agent-count'),
retryBtn: document.getElementById('retry-btn'),
};
let ws = null;
let autoRetries = 0;
let connectTimer = null;
function setStatus(state, message) {
dom.dot.className = 'dot ' + state;
dom.text.textContent = message;
dom.retryBtn.style.display = state === Status.OFFLINE ? 'block' : 'none';
}
function setAgentCount(n) {
dom.agents.textContent = n;
}
function cleanup() {
clearTimeout(connectTimer);
if (ws) {
ws.onopen = null;
ws.onclose = null;
ws.onerror = null;
ws.onmessage = null;
if (ws.readyState <= WebSocket.OPEN) ws.close();
ws = null;
}
}
function connect() {
cleanup();
setStatus(Status.CONNECTING, 'CONNECTING\u2026');
setAgentCount(0);
try {
ws = new WebSocket(HERMES_WS_URL);
} catch (err) {
console.error('[Workshop] WebSocket creation failed:', err);
onFail();
return;
}
connectTimer = setTimeout(function () {
console.warn('[Workshop] Connection timeout after ' + CONNECT_TIMEOUT_MS + 'ms');
cleanup();
onFail();
}, CONNECT_TIMEOUT_MS);
ws.onopen = function () {
clearTimeout(connectTimer);
autoRetries = 0;
setStatus(Status.ONLINE, 'ONLINE');
console.log('[Workshop] Connected to tower-hermes');
};
ws.onmessage = function (evt) {
try {
var msg = JSON.parse(evt.data);
if (typeof msg.agents === 'number') setAgentCount(msg.agents);
} catch (_) {
// non-JSON messages are ignored
}
};
ws.onclose = function () {
clearTimeout(connectTimer);
console.log('[Workshop] Connection closed');
onFail();
};
ws.onerror = function () {
clearTimeout(connectTimer);
console.error('[Workshop] WebSocket error');
// onclose will fire after this, which calls onFail
};
}
function onFail() {
if (autoRetries < MAX_AUTO_RETRIES) {
autoRetries++;
setStatus(Status.CONNECTING, 'RETRYING (' + autoRetries + '/' + MAX_AUTO_RETRIES + ')\u2026');
setTimeout(connect, RETRY_DELAY_MS);
} else {
setStatus(Status.OFFLINE, 'OFFLINE \u2014 backend unreachable');
}
}
// Manual retry resets the counter
dom.retryBtn.addEventListener('click', function () {
autoRetries = 0;
connect();
});
// Boot
export function initWorkshop(container) {
// TODO: Initialize 3D scene
// TODO: Load wizard character model
// TODO: Connect to Timmy presence WebSocket
console.log('[Workshop] Scene container ready:', container.id);
connect();
}
initWorkshop(document.getElementById('scene'));