@starlove/orbit 1.2.1 → 1.3.1
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/Dockerfile +2 -2
- package/README.md +16 -17
- package/SKILL-ORBIT.md +1 -1
- package/dist/full/index.html +225 -29
- package/dist/full/js/app.b8296a78.js +742 -0
- package/dist/full/js/app.b8296a78.js.map +7 -0
- package/dist/full/styles.65b79f8a.css +1 -0
- package/dist/full/version.json +2 -2
- package/docs/MCP_SETUP.md +6 -5
- package/docs/setup/orbit-init-wizard.md +3 -3
- package/package.json +16 -18
- package/public/index.html +223 -27
- package/public/js/app.js +4 -1
- package/public/js/board-menu.js +21 -8
- package/public/js/help-menu.js +31 -0
- package/public/js/main.js +4 -0
- package/public/js/settings.js +95 -3
- package/public/js/ticket-detail.js +240 -93
- package/public/js/topbar-notes.js +155 -0
- package/public/styles.css +224 -487
- package/src/cli/orbit.js +48 -10
- package/src/core/agents-md.js +15 -0
- package/src/core/board.js +6 -3
- package/src/core/boards.js +54 -11
- package/src/core/db.js +13 -0
- package/src/core/migrate.js +1 -1
- package/src/core/paths.js +1 -1
- package/src/core/registry.js +4 -3
- package/src/server.js +24 -2
- package/dist/full/js/app.ccc442f7.js +0 -706
- package/dist/full/js/app.ccc442f7.js.map +0 -7
- package/dist/full/styles.b7c19a07.css +0 -1
package/src/cli/orbit.js
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* copies managed SKILL-ORBIT.md into the target repo. Does not import board.js.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { spawnSync } from "node:child_process";
|
|
8
|
-
import { existsSync, mkdirSync, readFileSync, rmSync, unlinkSync } from "node:fs";
|
|
7
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
8
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, unlinkSync } from "node:fs";
|
|
9
|
+
import { homedir } from "node:os";
|
|
9
10
|
import { dirname, join, resolve } from "node:path";
|
|
10
11
|
import { fileURLToPath } from "node:url";
|
|
11
12
|
import { removeOrbitAgentsSection, syncAgentsMd, syncSkillOrbitMd } from "../core/agents-md.js";
|
|
@@ -17,20 +18,23 @@ function printUsage() {
|
|
|
17
18
|
orbit -v, --version Print the Orbit CLI version
|
|
18
19
|
orbit init [options] Provision .orbit/board.db + SKILL-ORBIT.md + AGENTS.md
|
|
19
20
|
orbit reset [options] Delete .orbit/ + SKILL-ORBIT.md + registry row
|
|
20
|
-
orbit
|
|
21
|
+
orbit run [options] Start the Orbit web app + HTTP API
|
|
21
22
|
orbit docker [options] Build/run Orbit's web app in Docker
|
|
22
23
|
orbit mcp [options] Start the stdio MCP server (for agent clients)
|
|
23
24
|
orbit dispatch [options] Dispatch a Hermes profile on a ticket
|
|
24
25
|
|
|
25
|
-
Options (init / reset /
|
|
26
|
+
Options (init / reset / run / docker / mcp / dispatch):
|
|
26
27
|
--cwd <dir> Project root (default: process.cwd())
|
|
27
28
|
|
|
28
29
|
Options (init only):
|
|
29
30
|
--no-ai Create the board with AI collaboration disabled
|
|
30
31
|
--example Create example onboarding tickets
|
|
31
32
|
|
|
32
|
-
Options (
|
|
33
|
-
--port <n> HTTP port (
|
|
33
|
+
Options (run / docker):
|
|
34
|
+
--port <n> HTTP port (run auto-starts at 13701; --port/$PORT is exact)
|
|
35
|
+
|
|
36
|
+
Options (run):
|
|
37
|
+
-d, --detach Run in the background and print the child PID/log path
|
|
34
38
|
|
|
35
39
|
Options (dispatch):
|
|
36
40
|
--board <slug-or-id> Local board to dispatch against (default: board for --cwd)
|
|
@@ -173,6 +177,7 @@ function parseArgs(argv) {
|
|
|
173
177
|
ai: true,
|
|
174
178
|
example: false,
|
|
175
179
|
port: null,
|
|
180
|
+
detachRequested: false,
|
|
176
181
|
image: "starscape-orbit:local",
|
|
177
182
|
dataDir: null,
|
|
178
183
|
containerName: null,
|
|
@@ -301,6 +306,7 @@ function parseArgs(argv) {
|
|
|
301
306
|
args.buildImage = false;
|
|
302
307
|
} else if (a === "-d" || a === "--detach") {
|
|
303
308
|
args.detach = true;
|
|
309
|
+
args.detachRequested = true;
|
|
304
310
|
} else if (a === "--foreground") {
|
|
305
311
|
args.detach = false;
|
|
306
312
|
args.foreground = true;
|
|
@@ -461,6 +467,37 @@ async function runServe(options) {
|
|
|
461
467
|
await import("../server.js");
|
|
462
468
|
}
|
|
463
469
|
|
|
470
|
+
function orbitDataDir() {
|
|
471
|
+
return process.env.DATA_DIR || join(homedir(), ".orbit");
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function startDetachedRun(options) {
|
|
475
|
+
const dataDir = orbitDataDir();
|
|
476
|
+
mkdirSync(dataDir, { recursive: true });
|
|
477
|
+
const logPath = join(dataDir, "orbit-run.log");
|
|
478
|
+
const logFd = openSync(logPath, "a");
|
|
479
|
+
const cliPath = fileURLToPath(import.meta.url);
|
|
480
|
+
const childArgs = [cliPath, "run", "--cwd", resolve(options.cwd)];
|
|
481
|
+
if (options.port) childArgs.push("--port", String(options.port));
|
|
482
|
+
const child = spawn(process.execPath, childArgs, {
|
|
483
|
+
cwd: process.cwd(),
|
|
484
|
+
detached: true,
|
|
485
|
+
env: { ...process.env },
|
|
486
|
+
stdio: ["ignore", logFd, logFd]
|
|
487
|
+
});
|
|
488
|
+
child.unref();
|
|
489
|
+
closeSync(logFd);
|
|
490
|
+
const port = options.port || process.env.PORT || 13701;
|
|
491
|
+
console.log("Started Orbit in detached mode.");
|
|
492
|
+
console.log(`PID: ${child.pid}`);
|
|
493
|
+
console.log(`Log: ${logPath}`);
|
|
494
|
+
console.log(
|
|
495
|
+
options.port || process.env.PORT
|
|
496
|
+
? `URL: http://localhost:${port}`
|
|
497
|
+
: `URL: starts at http://localhost:${port} (auto-increments if busy; see log)`
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
464
501
|
function containerPathFor(hostPath, fallbackPath) {
|
|
465
502
|
// Linux/macOS Docker can mount the same absolute path into the container,
|
|
466
503
|
// which keeps existing Orbit board metadata meaningful. Windows paths are
|
|
@@ -473,7 +510,7 @@ function buildDockerPlan(options) {
|
|
|
473
510
|
const dataDir = resolve(options.dataDir || resolve(projectRoot, ".orbit", "docker-data"));
|
|
474
511
|
const containerProjectRoot = containerPathFor(projectRoot, "/workspace");
|
|
475
512
|
const containerDataDir = containerPathFor(dataDir, "/data");
|
|
476
|
-
const port = String(options.port || process.env.PORT ||
|
|
513
|
+
const port = String(options.port || process.env.PORT || 13701);
|
|
477
514
|
|
|
478
515
|
if (!existsSync(projectRoot)) {
|
|
479
516
|
throw new Error(`Project root does not exist: ${projectRoot}`);
|
|
@@ -508,7 +545,7 @@ function buildDockerPlan(options) {
|
|
|
508
545
|
"-w",
|
|
509
546
|
containerProjectRoot,
|
|
510
547
|
options.image,
|
|
511
|
-
"
|
|
548
|
+
"run",
|
|
512
549
|
"--cwd",
|
|
513
550
|
containerProjectRoot,
|
|
514
551
|
"--port",
|
|
@@ -595,8 +632,9 @@ async function main() {
|
|
|
595
632
|
await runInit(args);
|
|
596
633
|
} else if (args.command === "reset") {
|
|
597
634
|
await runReset(args);
|
|
598
|
-
} else if (args.command === "serve") {
|
|
599
|
-
|
|
635
|
+
} else if (args.command === "run" || args.command === "serve") {
|
|
636
|
+
if (args.detachRequested && args.detach) startDetachedRun(args);
|
|
637
|
+
else await runServe(args);
|
|
600
638
|
} else if (args.command === "docker") {
|
|
601
639
|
await runDocker(args);
|
|
602
640
|
} else if (args.command === "mcp") {
|
package/src/core/agents-md.js
CHANGED
|
@@ -33,6 +33,21 @@ export function syncSkillOrbitMd(projectRoot, skillSrc) {
|
|
|
33
33
|
return { ok: true, status: "written", source: skillSrc, path: skillDest };
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Write both Orbit helper files (SKILL-ORBIT.md + the AGENTS.md Orbit section)
|
|
38
|
+
* into a project root. This is the shared core behind `orbit init` and the
|
|
39
|
+
* web-UI "Generate AI helper files" checkbox, so the two flows stay identical.
|
|
40
|
+
* Never throws — returns a result the caller can log or ignore.
|
|
41
|
+
*/
|
|
42
|
+
export function generateHelperFiles(projectRoot, skillSrc, logger = console.log) {
|
|
43
|
+
if (!existsSync(skillSrc)) {
|
|
44
|
+
return { ok: false, status: "missing_source", source: skillSrc };
|
|
45
|
+
}
|
|
46
|
+
const skill = syncSkillOrbitMd(projectRoot, skillSrc);
|
|
47
|
+
syncAgentsMd(projectRoot, {}, logger);
|
|
48
|
+
return { ok: true, status: skill.status, skill };
|
|
49
|
+
}
|
|
50
|
+
|
|
36
51
|
export function syncAgentsMd(projectRoot, _options = {}, logger = console.log) {
|
|
37
52
|
const agentsPath = resolve(projectRoot, "AGENTS.md");
|
|
38
53
|
const section = orbitAgentsSection();
|
package/src/core/board.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// 3. Iterate every registry board, ensuring the per-board schema exists
|
|
9
9
|
// and reindexing FTS for that board's DB. No "active board" singleton:
|
|
10
10
|
// each iteration opens its own connection.
|
|
11
|
-
// 4. When launched through `orbit
|
|
11
|
+
// 4. When launched through `orbit run`, refresh Orbit-managed
|
|
12
12
|
// SKILL-ORBIT.md copies for registered repo paths.
|
|
13
13
|
//
|
|
14
14
|
// Beyond startup this module just re-exports per-domain functions for
|
|
@@ -67,6 +67,9 @@ function syncManagedSkillOrbitFiles() {
|
|
|
67
67
|
const skillSrc = resolve(ROOT_DIR, "SKILL-ORBIT.md");
|
|
68
68
|
for (const row of listBoards()) {
|
|
69
69
|
if (!row.repo_path) continue;
|
|
70
|
+
// Respect boards created without "Generate AI helper files" — don't
|
|
71
|
+
// re-push SKILL-ORBIT.md into a repo the user opted out of.
|
|
72
|
+
if (row.manage_helper_files === 0) continue;
|
|
70
73
|
try {
|
|
71
74
|
const result = syncSkillOrbitMd(row.repo_path, skillSrc);
|
|
72
75
|
if (result.status === "missing_source") {
|
|
@@ -107,10 +110,10 @@ export const boardRuntime = {
|
|
|
107
110
|
port: PORT
|
|
108
111
|
};
|
|
109
112
|
|
|
110
|
-
export function startupSummary() {
|
|
113
|
+
export function startupSummary(port = PORT) {
|
|
111
114
|
const rows = listBoards();
|
|
112
115
|
return {
|
|
113
|
-
url: `http://localhost:${
|
|
116
|
+
url: `http://localhost:${port}`,
|
|
114
117
|
registryPath: REGISTRY_DB_PATH,
|
|
115
118
|
boardCount: rows.length,
|
|
116
119
|
boardPaths: rows.map((row) => row.db_path)
|
package/src/core/boards.js
CHANGED
|
@@ -9,8 +9,10 @@ import {
|
|
|
9
9
|
import { requireBoardAccess, requirePermission } from "./auth.js";
|
|
10
10
|
import { recordEvent } from "./events.js";
|
|
11
11
|
import { buildBoardFromRepo } from "./seed.js";
|
|
12
|
+
import { generateHelperFiles } from "./agents-md.js";
|
|
12
13
|
import { httpError, id, now, normalizePath, normalizeProjectEntryType, requiredString, slugify } from "./util.js";
|
|
13
14
|
import { DATA_DIR, MCP_SERVER_PATH, ROOT_DIR } from "./paths.js";
|
|
15
|
+
import { resolve } from "node:path";
|
|
14
16
|
|
|
15
17
|
/** Create a brand-new board on disk + register it. Does not require an
|
|
16
18
|
* existing ctx.board / ctx.db: this is registry-only. */
|
|
@@ -20,7 +22,13 @@ export function createBoard(body, ctx) {
|
|
|
20
22
|
|
|
21
23
|
const name = requiredString(body.name, "name");
|
|
22
24
|
const slug = slugify(body.slug || name);
|
|
23
|
-
|
|
25
|
+
// Folder is optional: a board without a repo_path is a pure kanban board (no
|
|
26
|
+
// agent dispatch, no managed helper files). When present it is the coding
|
|
27
|
+
// project where SKILL-ORBIT.md / AGENTS.md live for the AI agent's reference.
|
|
28
|
+
const repoPath = normalizePath(body.repo_path || body.system_path || "");
|
|
29
|
+
// Only meaningful with a folder; default on so the common "link my repo" path
|
|
30
|
+
// keeps writing the docs. Stored so the serve-time refresh respects opt-out.
|
|
31
|
+
const manageHelperFiles = repoPath ? body.manage_helper_files !== false : false;
|
|
24
32
|
const reg = getRegistry();
|
|
25
33
|
if (reg.prepare("SELECT id FROM boards WHERE slug = ?").get(slug)) {
|
|
26
34
|
throw httpError(409, "board_slug_taken");
|
|
@@ -31,7 +39,7 @@ export function createBoard(body, ctx) {
|
|
|
31
39
|
|
|
32
40
|
const boardId = id();
|
|
33
41
|
const time = now();
|
|
34
|
-
const sniff = buildBoardFromRepo(repoPath);
|
|
42
|
+
const sniff = repoPath ? buildBoardFromRepo(repoPath) : { repoUrl: "", defaultBranch: "main" };
|
|
35
43
|
|
|
36
44
|
const aiEnabled = body.ai_enabled === undefined ? true : Boolean(body.ai_enabled);
|
|
37
45
|
const defaultStates = body.states || (aiEnabled ? [
|
|
@@ -92,11 +100,23 @@ export function createBoard(body, ctx) {
|
|
|
92
100
|
db_path: dbPath,
|
|
93
101
|
repo_url: body.repo_url || sniff.repoUrl || "",
|
|
94
102
|
default_branch: body.default_branch || sniff.defaultBranch || "main",
|
|
103
|
+
manage_helper_files: manageHelperFiles ? 1 : 0,
|
|
95
104
|
last_active_at: time,
|
|
96
105
|
created_at: time,
|
|
97
106
|
updated_at: time
|
|
98
107
|
});
|
|
99
108
|
|
|
109
|
+
// Mirror `orbit init`: write SKILL-ORBIT.md + the AGENTS.md Orbit section into
|
|
110
|
+
// the project so the browser flow leaves the same files on disk. Best-effort —
|
|
111
|
+
// a board is still created if the files can't be written.
|
|
112
|
+
if (manageHelperFiles) {
|
|
113
|
+
try {
|
|
114
|
+
generateHelperFiles(repoPath, resolve(ROOT_DIR, "SKILL-ORBIT.md"));
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.warn(`[orbit] Could not write helper files in ${repoPath}: ${error.message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
100
120
|
return boardById(db, boardId);
|
|
101
121
|
}
|
|
102
122
|
|
|
@@ -232,25 +252,47 @@ export function updateBoard(boardId, body, ctx) {
|
|
|
232
252
|
values.push(body.ai_enabled ? 1 : 0);
|
|
233
253
|
}
|
|
234
254
|
|
|
235
|
-
|
|
236
|
-
sets
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
255
|
+
// manage_helper_files lives on the registry row (not the per-board DB), so it
|
|
256
|
+
// is handled separately from `sets`. A request that only toggles it must
|
|
257
|
+
// still be processed even when no per-board columns change.
|
|
258
|
+
const manageHelperFiles =
|
|
259
|
+
body.manage_helper_files === undefined ? undefined : Boolean(body.manage_helper_files);
|
|
260
|
+
|
|
261
|
+
if (sets.length === 0 && manageHelperFiles === undefined) return innerBoard;
|
|
262
|
+
|
|
263
|
+
if (sets.length > 0) {
|
|
264
|
+
sets.push("updated_at = ?");
|
|
265
|
+
values.push(now(), boardId);
|
|
266
|
+
tx(db, () => {
|
|
267
|
+
db.prepare(`UPDATE boards SET ${sets.join(", ")} WHERE id = ?`).run(...values);
|
|
268
|
+
if (aiTurningOn) ensureAgentLanes(db, boardId);
|
|
269
|
+
recordEvent(db, boardId, "board_updated", null, actor.name, { board_id: boardId, fields: Object.keys(body) });
|
|
270
|
+
});
|
|
271
|
+
}
|
|
243
272
|
|
|
244
273
|
// Mirror the changed fields into the registry row so cross-board listings
|
|
245
274
|
// stay coherent without needing a second source of truth.
|
|
246
275
|
const after = boardById(db, boardId);
|
|
276
|
+
const repoPath = normalizePath(after.system_path || "");
|
|
247
277
|
updateBoardMeta(boardId, {
|
|
248
278
|
name: after.name,
|
|
249
279
|
repo_url: after.repo_url,
|
|
250
280
|
default_branch: after.default_branch,
|
|
251
|
-
repo_path:
|
|
281
|
+
repo_path: repoPath,
|
|
282
|
+
...(manageHelperFiles === undefined ? {} : { manage_helper_files: manageHelperFiles ? 1 : 0 })
|
|
252
283
|
});
|
|
253
284
|
|
|
285
|
+
// Generating the helper files is the active half of turning the toggle on:
|
|
286
|
+
// write them now rather than waiting for the next `orbit run` refresh.
|
|
287
|
+
// Turning it off just stops the managed refresh; existing files are left.
|
|
288
|
+
if (manageHelperFiles && repoPath) {
|
|
289
|
+
try {
|
|
290
|
+
generateHelperFiles(repoPath, resolve(ROOT_DIR, "SKILL-ORBIT.md"));
|
|
291
|
+
} catch (error) {
|
|
292
|
+
console.warn(`[orbit] Could not write helper files in ${repoPath}: ${error.message}`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
254
296
|
return after;
|
|
255
297
|
}
|
|
256
298
|
|
|
@@ -284,6 +326,7 @@ export function boardManual(boardId, ctx, options = {}) {
|
|
|
284
326
|
project_notes: innerBoard.project_notes,
|
|
285
327
|
agent_instructions: innerBoard.agent_instructions,
|
|
286
328
|
ai_enabled: innerBoard.ai_enabled,
|
|
329
|
+
manage_helper_files: ctx.board.manage_helper_files,
|
|
287
330
|
created_at: innerBoard.created_at,
|
|
288
331
|
updated_at: innerBoard.updated_at
|
|
289
332
|
},
|
package/src/core/db.js
CHANGED
|
@@ -103,6 +103,15 @@ export function getRegistry() {
|
|
|
103
103
|
return openConnection(REGISTRY_DB_PATH);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
/** Add a column to an existing table only if it is missing. Idempotent: safe to
|
|
107
|
+
* call on every startup. SQLite has no `ADD COLUMN IF NOT EXISTS`, so we probe
|
|
108
|
+
* `PRAGMA table_info` first. */
|
|
109
|
+
function ensureColumn(reg, table, column, definition) {
|
|
110
|
+
const cols = reg.prepare(`PRAGMA table_info(${table})`).all();
|
|
111
|
+
if (cols.some((c) => c.name === column)) return;
|
|
112
|
+
reg.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition};`);
|
|
113
|
+
}
|
|
114
|
+
|
|
106
115
|
/** Registry metadata table (not the per-board `boards` content row). */
|
|
107
116
|
export function createRegistrySchema(reg = getRegistry()) {
|
|
108
117
|
reg.exec(`
|
|
@@ -120,6 +129,10 @@ export function createRegistrySchema(reg = getRegistry()) {
|
|
|
120
129
|
);
|
|
121
130
|
CREATE INDEX IF NOT EXISTS idx_registry_boards_repo_path ON boards(repo_path);
|
|
122
131
|
`);
|
|
132
|
+
// Whether Orbit keeps SKILL-ORBIT.md / AGENTS.md in sync inside repo_path.
|
|
133
|
+
// Defaults to 1 so pre-existing boards (created when files were always
|
|
134
|
+
// written) keep their managed copies refreshed on startup.
|
|
135
|
+
ensureColumn(reg, "boards", "manage_helper_files", "INTEGER NOT NULL DEFAULT 1");
|
|
123
136
|
}
|
|
124
137
|
|
|
125
138
|
/** Full Kanban schema on a per-board database connection. */
|
package/src/core/migrate.js
CHANGED
|
@@ -178,7 +178,7 @@ export function migrateInRepoBoards() {
|
|
|
178
178
|
console.error(`\n[orbit] ⚠ Could not auto-migrate ${n} board${n > 1 ? "s" : ""} to central storage.`);
|
|
179
179
|
console.error(`${sep} These boards continue to work from their current paths.`);
|
|
180
180
|
console.error(`${sep} To complete the migration manually, run the commands below and`);
|
|
181
|
-
console.error(`${sep} restart orbit
|
|
181
|
+
console.error(`${sep} restart orbit run:\n`);
|
|
182
182
|
for (const r of failed) {
|
|
183
183
|
const slash = process.platform === "win32" ? "\\" : "/";
|
|
184
184
|
const destDir = dirname(r.newPath).replace(/\//g, slash);
|
package/src/core/paths.js
CHANGED
|
@@ -22,7 +22,7 @@ export const EXPORT_DIR = join(DATA_DIR, "exports");
|
|
|
22
22
|
export const BACKUP_DIR = join(DATA_DIR, "backups");
|
|
23
23
|
/** Central directory for dispatch run artifacts: DATA_DIR/dispatch-runs/<board-slug>/<run-id>/ */
|
|
24
24
|
export const DISPATCH_RUNS_DIR = join(DATA_DIR, "dispatch-runs");
|
|
25
|
-
export const PORT = Number(process.env.PORT ||
|
|
25
|
+
export const PORT = Number(process.env.PORT || 13701);
|
|
26
26
|
|
|
27
27
|
mkdirSync(DATA_DIR, { recursive: true });
|
|
28
28
|
mkdirSync(BOARDS_DIR, { recursive: true });
|
package/src/core/registry.js
CHANGED
|
@@ -60,8 +60,8 @@ export function insertBoard(row) {
|
|
|
60
60
|
const t = row.created_at || now();
|
|
61
61
|
getRegistry()
|
|
62
62
|
.prepare(
|
|
63
|
-
`INSERT INTO boards (id, slug, name, repo_path, db_path, repo_url, default_branch, last_active_at, created_at, updated_at)
|
|
64
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
63
|
+
`INSERT INTO boards (id, slug, name, repo_path, db_path, repo_url, default_branch, manage_helper_files, last_active_at, created_at, updated_at)
|
|
64
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
65
65
|
)
|
|
66
66
|
.run(
|
|
67
67
|
row.id,
|
|
@@ -71,6 +71,7 @@ export function insertBoard(row) {
|
|
|
71
71
|
row.db_path,
|
|
72
72
|
row.repo_url || "",
|
|
73
73
|
row.default_branch || "main",
|
|
74
|
+
row.manage_helper_files === undefined ? 1 : row.manage_helper_files ? 1 : 0,
|
|
74
75
|
row.last_active_at || t,
|
|
75
76
|
t,
|
|
76
77
|
row.updated_at || t
|
|
@@ -80,7 +81,7 @@ export function insertBoard(row) {
|
|
|
80
81
|
export function updateBoardMeta(boardId, patch) {
|
|
81
82
|
const sets = [];
|
|
82
83
|
const vals = [];
|
|
83
|
-
for (const k of ["slug", "name", "repo_path", "db_path", "repo_url", "default_branch"]) {
|
|
84
|
+
for (const k of ["slug", "name", "repo_path", "db_path", "repo_url", "default_branch", "manage_helper_files"]) {
|
|
84
85
|
if (patch[k] !== undefined) {
|
|
85
86
|
sets.push(`${k} = ?`);
|
|
86
87
|
vals.push(patch[k]);
|
package/src/server.js
CHANGED
|
@@ -40,8 +40,30 @@ const server = createServer(async (req, res) => {
|
|
|
40
40
|
}
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
const
|
|
44
|
-
|
|
43
|
+
const requestedPort = boardRuntime.port;
|
|
44
|
+
const hasExplicitPort = Boolean(process.env.PORT);
|
|
45
|
+
const maxAutoPort = requestedPort + 99;
|
|
46
|
+
let currentPort = requestedPort;
|
|
47
|
+
|
|
48
|
+
server.on("error", (error) => {
|
|
49
|
+
if (!hasExplicitPort && error?.code === "EADDRINUSE" && currentPort < maxAutoPort) {
|
|
50
|
+
currentPort += 1;
|
|
51
|
+
console.warn(`[orbit] Port ${currentPort - 1} is in use; trying ${currentPort}.`);
|
|
52
|
+
server.listen(currentPort);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.error(error);
|
|
57
|
+
closeAllConnections();
|
|
58
|
+
process.exit(1);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
server.listen(currentPort);
|
|
62
|
+
|
|
63
|
+
server.on("listening", () => {
|
|
64
|
+
const address = server.address();
|
|
65
|
+
const port = typeof address === "object" && address ? address.port : currentPort;
|
|
66
|
+
const summary = startupSummary(port);
|
|
45
67
|
console.log(`Starscape Orbit listening on ${summary.url}`);
|
|
46
68
|
console.log(`Registry: ${summary.registryPath}`);
|
|
47
69
|
console.log(`Boards (${summary.boardCount}):`);
|