plotlink-ows 1.0.4 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/lib/paths.ts +8 -1
- package/app/prisma/schema.prisma +1 -1
- package/app/routes/stories.ts +1 -4
- package/app/routes/terminal.ts +2 -4
- package/app/server.ts +60 -9
- package/app/web/components/TerminalPanel.tsx +3 -6
- package/app/web/styles.css +24 -12
- package/package.json +1 -1
- package/scripts/fix-index-status.ts +1 -4
package/app/lib/paths.ts
CHANGED
|
@@ -5,5 +5,12 @@ import fs from "fs";
|
|
|
5
5
|
/** All user state lives in ~/.plotlink-ows/ — survives npx reinstalls */
|
|
6
6
|
export const CONFIG_DIR = path.join(os.homedir(), ".plotlink-ows");
|
|
7
7
|
export const ENV_FILE = path.join(CONFIG_DIR, ".env");
|
|
8
|
-
|
|
8
|
+
export const STORIES_DIR = path.join(CONFIG_DIR, "stories");
|
|
9
|
+
export const DATA_DIR = path.join(CONFIG_DIR, "data");
|
|
10
|
+
export const DB_PATH = path.join(DATA_DIR, "local.db");
|
|
11
|
+
export const DATABASE_URL = `file:${DB_PATH}`;
|
|
12
|
+
|
|
13
|
+
// Ensure persistent directories exist on import
|
|
9
14
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
15
|
+
fs.mkdirSync(STORIES_DIR, { recursive: true });
|
|
16
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
package/app/prisma/schema.prisma
CHANGED
package/app/routes/stories.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const STORIES_DIR = path.join(__dirname, "..", "..", "stories");
|
|
4
|
+
import { STORIES_DIR } from "../lib/paths";
|
|
8
5
|
|
|
9
6
|
const stories = new Hono();
|
|
10
7
|
|
package/app/routes/terminal.ts
CHANGED
|
@@ -2,13 +2,11 @@ import { Hono } from "hono";
|
|
|
2
2
|
import * as pty from "node-pty";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import fs from "fs";
|
|
5
|
-
import { fileURLToPath } from "url";
|
|
6
5
|
import { randomUUID } from "crypto";
|
|
6
|
+
import { STORIES_DIR, DATA_DIR } from "../lib/paths";
|
|
7
7
|
|
|
8
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
const STORIES_DIR = path.join(__dirname, "..", "..", "stories");
|
|
10
8
|
const MAX_SESSIONS = 5;
|
|
11
|
-
const SESSION_FILE = path.join(
|
|
9
|
+
const SESSION_FILE = path.join(DATA_DIR, "terminal-sessions.json");
|
|
12
10
|
|
|
13
11
|
const terminal = new Hono();
|
|
14
12
|
|
package/app/server.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import dotenv from "dotenv";
|
|
2
|
+
import os from "os";
|
|
2
3
|
import path from "path";
|
|
3
4
|
import { fileURLToPath } from "url";
|
|
4
|
-
import { ENV_FILE } from "./lib/paths";
|
|
5
|
+
import { ENV_FILE, DATA_DIR, STORIES_DIR, DATABASE_URL } from "./lib/paths";
|
|
6
|
+
|
|
7
|
+
// Set DATABASE_URL before any Prisma imports
|
|
8
|
+
process.env.DATABASE_URL = DATABASE_URL;
|
|
5
9
|
|
|
6
10
|
// Load .env from ~/.plotlink-ows/ before anything else
|
|
7
11
|
const __dirnamePre = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -57,25 +61,72 @@ if (fs.existsSync(distPath)) {
|
|
|
57
61
|
});
|
|
58
62
|
}
|
|
59
63
|
|
|
64
|
+
/** Copy story directories from a source dir into STORIES_DIR, skipping duplicates */
|
|
65
|
+
function migrateStoriesFrom(srcDir: string, label: string) {
|
|
66
|
+
if (!fs.existsSync(srcDir) || srcDir === STORIES_DIR) return;
|
|
67
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true })
|
|
68
|
+
.filter((d) => d.isDirectory() && !d.name.startsWith(".") && d.name !== "_example");
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
const dest = path.join(STORIES_DIR, entry.name);
|
|
71
|
+
if (fs.existsSync(dest)) continue;
|
|
72
|
+
try {
|
|
73
|
+
fs.renameSync(path.join(srcDir, entry.name), dest);
|
|
74
|
+
} catch {
|
|
75
|
+
fs.cpSync(path.join(srcDir, entry.name), dest, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
console.log(` Migrated story "${entry.name}" from ${label}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Copy a single file if source exists and destination doesn't */
|
|
82
|
+
function migrateFileFrom(src: string, dest: string, label: string) {
|
|
83
|
+
if (fs.existsSync(src) && !fs.existsSync(dest)) {
|
|
84
|
+
fs.copyFileSync(src, dest);
|
|
85
|
+
console.log(` Migrated ${label} → ${path.dirname(dest)}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Migrate stories/data from old locations to ~/.plotlink-ows/ */
|
|
90
|
+
function migrateOldData() {
|
|
91
|
+
// 1. Scan all previous npx cache directories
|
|
92
|
+
const npxBase = path.join(os.homedir(), ".npm", "_npx");
|
|
93
|
+
if (fs.existsSync(npxBase)) {
|
|
94
|
+
try {
|
|
95
|
+
// Only migrate stories from npx caches — db/sessions are singletons and
|
|
96
|
+
// picking from a random cache entry could restore stale state
|
|
97
|
+
for (const hash of fs.readdirSync(npxBase)) {
|
|
98
|
+
const pkgRoot = path.join(npxBase, hash, "node_modules", "plotlink-ows");
|
|
99
|
+
migrateStoriesFrom(path.join(pkgRoot, "stories"), `npx cache (${hash.slice(0, 8)})`);
|
|
100
|
+
}
|
|
101
|
+
} catch { /* npx cache scan best-effort */ }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 2. Current package-relative path (dev → npx transition)
|
|
105
|
+
const oldStoriesDir = path.join(__dirname, "..", "stories");
|
|
106
|
+
const oldDataDir = path.join(__dirname, "..", "data");
|
|
107
|
+
migrateStoriesFrom(oldStoriesDir, "package directory");
|
|
108
|
+
migrateFileFrom(path.join(oldDataDir, "local.db"), path.join(DATA_DIR, "local.db"), "database");
|
|
109
|
+
migrateFileFrom(
|
|
110
|
+
path.join(oldDataDir, "terminal-sessions.json"),
|
|
111
|
+
path.join(DATA_DIR, "terminal-sessions.json"),
|
|
112
|
+
"terminal sessions",
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
60
116
|
async function start() {
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
|
117
|
+
// Auto-migrate from old package-relative paths
|
|
118
|
+
migrateOldData();
|
|
64
119
|
|
|
65
120
|
// Run Prisma db push to ensure schema is up to date
|
|
66
121
|
const schemaPath = path.join(__dirname, "prisma", "schema.prisma");
|
|
67
122
|
execSync(`npx prisma db push --schema ${schemaPath} --skip-generate`, {
|
|
68
123
|
stdio: "inherit",
|
|
69
|
-
env: { ...process.env, DATABASE_URL
|
|
124
|
+
env: { ...process.env, DATABASE_URL },
|
|
70
125
|
});
|
|
71
126
|
|
|
72
127
|
// Initialize database connection
|
|
73
128
|
await initDb();
|
|
74
129
|
|
|
75
|
-
// Ensure stories directory exists
|
|
76
|
-
const storiesDir = path.join(__dirname, "..", "stories");
|
|
77
|
-
if (!fs.existsSync(storiesDir)) fs.mkdirSync(storiesDir, { recursive: true });
|
|
78
|
-
|
|
79
130
|
const port = Number(process.env.APP_PORT) || 7777;
|
|
80
131
|
const server = serve({ fetch: app.fetch, port }, (info) => {
|
|
81
132
|
console.log(`\n PlotLink OWS running at http://localhost:${info.port}\n`);
|
|
@@ -36,7 +36,7 @@ const THEME = {
|
|
|
36
36
|
blue: "#4A6FA5",
|
|
37
37
|
magenta: "#7B4B8A",
|
|
38
38
|
cyan: "#3D7A7A",
|
|
39
|
-
white: "#
|
|
39
|
+
white: "#E6DDD0", // subtle cream tint for input line backgrounds
|
|
40
40
|
brightBlack: "#8B7355",
|
|
41
41
|
brightRed: "#B85C5C", // muted red — readable as text, soft as diff bg
|
|
42
42
|
brightGreen: "#5A8A5A", // muted green — readable as text, soft as diff bg
|
|
@@ -184,6 +184,8 @@ export function TerminalPanel({ token, storyName, authFetch, onSelectStory }: Te
|
|
|
184
184
|
container.style.width = "100%";
|
|
185
185
|
container.style.height = "100%";
|
|
186
186
|
container.style.display = "none";
|
|
187
|
+
container.style.paddingLeft = "10px";
|
|
188
|
+
container.style.boxSizing = "border-box";
|
|
187
189
|
wrapperRef.current.appendChild(container);
|
|
188
190
|
|
|
189
191
|
const term = new Terminal({
|
|
@@ -207,11 +209,6 @@ export function TerminalPanel({ token, storyName, authFetch, onSelectStory }: Te
|
|
|
207
209
|
term.loadAddon(serialize);
|
|
208
210
|
term.open(container);
|
|
209
211
|
|
|
210
|
-
// Apply padding to term.element so FitAddon measures correctly
|
|
211
|
-
if (term.element) {
|
|
212
|
-
term.element.style.paddingLeft = "10px";
|
|
213
|
-
}
|
|
214
|
-
|
|
215
212
|
const session: TerminalSession = { term, fit, serialize, ws: null, container, observer: null as unknown as ResizeObserver, connected: false };
|
|
216
213
|
|
|
217
214
|
const observer = new ResizeObserver(() => {
|
package/app/web/styles.css
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
@plugin "@tailwindcss/typography";
|
|
3
3
|
|
|
4
|
+
@theme inline {
|
|
5
|
+
--color-background: #E8DFD0;
|
|
6
|
+
--color-foreground: #2C1810;
|
|
7
|
+
--color-surface: #F0EBE1;
|
|
8
|
+
--color-muted: #8B7355;
|
|
9
|
+
--color-accent: #8B4513;
|
|
10
|
+
--color-accent-dim: #6B3410;
|
|
11
|
+
--color-border: #D4C5B0;
|
|
12
|
+
--color-error: #CC3333;
|
|
13
|
+
--default-font-family: "Inter", system-ui, -apple-system, sans-serif;
|
|
14
|
+
}
|
|
15
|
+
|
|
4
16
|
:root {
|
|
5
17
|
--bg: #E8DFD0;
|
|
6
18
|
--bg-surface: #F0EBE1;
|
|
@@ -14,18 +26,6 @@
|
|
|
14
26
|
--paper-bg: #F5F0E8;
|
|
15
27
|
}
|
|
16
28
|
|
|
17
|
-
@theme inline {
|
|
18
|
-
--color-background: var(--bg);
|
|
19
|
-
--color-foreground: var(--text);
|
|
20
|
-
--color-surface: var(--bg-surface);
|
|
21
|
-
--color-muted: var(--text-muted);
|
|
22
|
-
--color-accent: var(--accent);
|
|
23
|
-
--color-accent-dim: var(--accent-dim);
|
|
24
|
-
--color-border: var(--border);
|
|
25
|
-
--color-error: var(--error);
|
|
26
|
-
--default-font-family: "Inter", system-ui, -apple-system, sans-serif;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
29
|
body {
|
|
30
30
|
background: var(--bg);
|
|
31
31
|
color: var(--text);
|
|
@@ -67,3 +67,15 @@ code, pre {
|
|
|
67
67
|
opacity: 1 !important;
|
|
68
68
|
color: #8B7355 !important;
|
|
69
69
|
}
|
|
70
|
+
|
|
71
|
+
/* Remove left vertical line artifact from xterm container */
|
|
72
|
+
.xterm {
|
|
73
|
+
border: none !important;
|
|
74
|
+
outline: none !important;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.xterm-viewport {
|
|
78
|
+
border: none !important;
|
|
79
|
+
outline: none !important;
|
|
80
|
+
}
|
|
81
|
+
|
package/package.json
CHANGED
|
@@ -10,10 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import fs from "fs";
|
|
12
12
|
import path from "path";
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
-
const STORIES_DIR = path.join(__dirname, "..", "stories");
|
|
13
|
+
import { STORIES_DIR } from "../app/lib/paths";
|
|
17
14
|
|
|
18
15
|
interface FileStatus {
|
|
19
16
|
file: string;
|