@launchsecure/launch-kit 0.0.34 → 0.0.35

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.
@@ -1530,6 +1530,73 @@ var init_manifest = __esm({
1530
1530
  }
1531
1531
  });
1532
1532
 
1533
+ // src/server/orbit/active.ts
1534
+ function anchorPaths(projectRoot) {
1535
+ const dir = (0, import_node_path7.join)(projectRoot, LAUNCHSECURE_DIR, "orbit");
1536
+ return { dir, current: (0, import_node_path7.join)(dir, "current"), activeJson: (0, import_node_path7.join)(dir, "active.json") };
1537
+ }
1538
+ function present(p) {
1539
+ try {
1540
+ (0, import_node_fs10.lstatSync)(p);
1541
+ return true;
1542
+ } catch {
1543
+ return false;
1544
+ }
1545
+ }
1546
+ function setActiveOrbit(entry, envFileName) {
1547
+ const paths = anchorPaths(entry.projectRoot);
1548
+ (0, import_node_fs10.mkdirSync)(paths.dir, { recursive: true });
1549
+ const tmpLink = `${paths.current}.tmp.${process.pid}`;
1550
+ if (present(tmpLink)) (0, import_node_fs10.rmSync)(tmpLink, { force: true });
1551
+ (0, import_node_fs10.symlinkSync)(entry.path, tmpLink);
1552
+ (0, import_node_fs10.renameSync)(tmpLink, paths.current);
1553
+ const active = {
1554
+ slug: entry.slug,
1555
+ branch: entry.branch,
1556
+ path: entry.path,
1557
+ envFile: (0, import_node_path7.join)(entry.path, envFileName),
1558
+ projectRoot: entry.projectRoot,
1559
+ anchor: paths.current,
1560
+ switchedAt: (/* @__PURE__ */ new Date()).toISOString()
1561
+ };
1562
+ const tmpJson = `${paths.activeJson}.tmp.${process.pid}`;
1563
+ (0, import_node_fs10.writeFileSync)(tmpJson, JSON.stringify(active, null, 2) + "\n", "utf-8");
1564
+ (0, import_node_fs10.renameSync)(tmpJson, paths.activeJson);
1565
+ return active;
1566
+ }
1567
+ function clearActiveOrbit(projectRoot) {
1568
+ const paths = anchorPaths(projectRoot);
1569
+ let cleared = false;
1570
+ for (const p of [paths.current, paths.activeJson]) {
1571
+ if (present(p)) {
1572
+ try {
1573
+ (0, import_node_fs10.rmSync)(p, { force: true });
1574
+ cleared = true;
1575
+ } catch {
1576
+ }
1577
+ }
1578
+ }
1579
+ return cleared;
1580
+ }
1581
+ function readActiveOrbit(projectRoot) {
1582
+ const { activeJson } = anchorPaths(projectRoot);
1583
+ if (!present(activeJson)) return null;
1584
+ try {
1585
+ return JSON.parse((0, import_node_fs10.readFileSync)(activeJson, "utf-8"));
1586
+ } catch {
1587
+ return null;
1588
+ }
1589
+ }
1590
+ var import_node_fs10, import_node_path7;
1591
+ var init_active = __esm({
1592
+ "src/server/orbit/active.ts"() {
1593
+ "use strict";
1594
+ import_node_fs10 = require("node:fs");
1595
+ import_node_path7 = require("node:path");
1596
+ init_launch_kit_paths();
1597
+ }
1598
+ });
1599
+
1533
1600
  // src/server/orbit/slug.ts
1534
1601
  function slugify(branch) {
1535
1602
  let s = branch.toLowerCase();
@@ -1556,7 +1623,7 @@ async function create(opts) {
1556
1623
  const envFileName = opts.envFileName ?? manifest.envFile ?? DEFAULT_ENV_FILE;
1557
1624
  const ctx = {
1558
1625
  projectRoot: opts.projectRoot,
1559
- manifestPath: (0, import_node_path7.join)(opts.projectRoot, "orbit.json"),
1626
+ manifestPath: (0, import_node_path8.join)(opts.projectRoot, "orbit.json"),
1560
1627
  manifest,
1561
1628
  logger: opts.logger,
1562
1629
  envFileName
@@ -1610,13 +1677,13 @@ async function create(opts) {
1610
1677
  Object.assign(rewriteFns, adapter.envRewrites(state, ctx));
1611
1678
  }
1612
1679
  }
1613
- const srcEnv = (0, import_node_path7.join)(opts.projectRoot, envFileName);
1614
- const dstEnv = (0, import_node_path7.join)(worktreeState.path, envFileName);
1615
- if ((0, import_node_fs10.existsSync)(srcEnv) && !(0, import_node_fs10.existsSync)(dstEnv)) {
1680
+ const srcEnv = (0, import_node_path8.join)(opts.projectRoot, envFileName);
1681
+ const dstEnv = (0, import_node_path8.join)(worktreeState.path, envFileName);
1682
+ if ((0, import_node_fs11.existsSync)(srcEnv) && !(0, import_node_fs11.existsSync)(dstEnv)) {
1616
1683
  opts.logger.step(`copy ${envFileName} into worktree`);
1617
1684
  copyEnvFile(srcEnv, dstEnv);
1618
1685
  }
1619
- if ((0, import_node_fs10.existsSync)(dstEnv) && Object.keys(rewriteFns).length > 0) {
1686
+ if ((0, import_node_fs11.existsSync)(dstEnv) && Object.keys(rewriteFns).length > 0) {
1620
1687
  opts.logger.step(`rewrite ${envFileName} (${Object.keys(rewriteFns).join(", ")})`);
1621
1688
  const r = rewriteEnvFile(dstEnv, rewriteFns);
1622
1689
  if (r.missing.length > 0) {
@@ -1636,7 +1703,7 @@ async function create(opts) {
1636
1703
  opts.logger.ok(`registered ${slug}`);
1637
1704
  try {
1638
1705
  opts.logger.step(`generate chart graph for worktree`);
1639
- const chartEntry = (0, import_node_path7.join)(__dirname, "graph-mcp-entry.js");
1706
+ const chartEntry = (0, import_node_path8.join)(__dirname, "graph-mcp-entry.js");
1640
1707
  const result = (0, import_node_child_process8.spawnSync)(
1641
1708
  process.execPath,
1642
1709
  [chartEntry, "generate"],
@@ -1688,7 +1755,7 @@ async function drop(opts) {
1688
1755
  const manifest = loadManifestOrThrow(opts.projectRoot);
1689
1756
  const ctx = {
1690
1757
  projectRoot: opts.projectRoot,
1691
- manifestPath: (0, import_node_path7.join)(opts.projectRoot, "orbit.json"),
1758
+ manifestPath: (0, import_node_path8.join)(opts.projectRoot, "orbit.json"),
1692
1759
  manifest,
1693
1760
  logger: opts.logger,
1694
1761
  envFileName: manifest.envFile ?? DEFAULT_ENV_FILE
@@ -1709,7 +1776,7 @@ async function drop(opts) {
1709
1776
  if (result.backupPath) backups[ref.name] = result.backupPath;
1710
1777
  freed[ref.name] = state.state;
1711
1778
  }
1712
- if ((0, import_node_fs10.existsSync)(entry.path)) {
1779
+ if ((0, import_node_fs11.existsSync)(entry.path)) {
1713
1780
  const blockers = findBlockingPids(entry.path);
1714
1781
  if (blockers.length > 0) {
1715
1782
  opts.logger.warn(`${blockers.length} process(es) holding files in worktree \u2014 terminating:`);
@@ -1728,15 +1795,19 @@ async function drop(opts) {
1728
1795
  } catch (e) {
1729
1796
  opts.logger.warn(`worktree remove failed (continuing anyway): ${e.message}`);
1730
1797
  }
1731
- if ((0, import_node_fs10.existsSync)(entry.path)) {
1798
+ if ((0, import_node_fs11.existsSync)(entry.path)) {
1732
1799
  opts.logger.step(`remove leftover dir at ${entry.path}`);
1733
1800
  try {
1734
- (0, import_node_fs10.rmSync)(entry.path, { recursive: true, force: true });
1801
+ (0, import_node_fs11.rmSync)(entry.path, { recursive: true, force: true });
1735
1802
  } catch (e) {
1736
1803
  opts.logger.warn(`leftover dir cleanup failed: ${e.message}`);
1737
1804
  }
1738
1805
  }
1739
1806
  await deregisterWorktree(slug);
1807
+ if (readActiveOrbit(opts.projectRoot)?.slug === slug) {
1808
+ clearActiveOrbit(opts.projectRoot);
1809
+ opts.logger.step("cleared active-orbit anchor (current)");
1810
+ }
1740
1811
  opts.logger.ok(`dropped ${slug}`);
1741
1812
  return { slug, backups, freed };
1742
1813
  }
@@ -1753,12 +1824,19 @@ function switchTo(opts) {
1753
1824
  const slug = slugify(opts.branch);
1754
1825
  const entry = lookupWorktree(slug);
1755
1826
  if (!entry) throw new Error(`no orbit registered for branch "${opts.branch}" (slug ${slug})`);
1827
+ const active = setActiveOrbit(entry, DEFAULT_ENV_FILE);
1756
1828
  return {
1757
1829
  path: entry.path,
1758
- envFile: (0, import_node_path7.join)(entry.path, DEFAULT_ENV_FILE),
1830
+ envFile: active.envFile,
1831
+ anchor: active.anchor,
1832
+ activeJson: anchorPaths(entry.projectRoot).activeJson,
1833
+ nextAction: `cd "${active.anchor}"`,
1759
1834
  entry
1760
1835
  };
1761
1836
  }
1837
+ function deactivate(opts) {
1838
+ return { cleared: clearActiveOrbit(opts.projectRoot), projectRoot: opts.projectRoot };
1839
+ }
1762
1840
  function doctor() {
1763
1841
  const issues = [];
1764
1842
  const trackedByRoot = /* @__PURE__ */ new Map();
@@ -1781,7 +1859,7 @@ function doctor() {
1781
1859
  return s;
1782
1860
  };
1783
1861
  for (const w of listWorktrees()) {
1784
- if (!(0, import_node_fs10.existsSync)(w.path)) {
1862
+ if (!(0, import_node_fs11.existsSync)(w.path)) {
1785
1863
  issues.push({ kind: "missing-worktree", slug: w.slug, detail: `path ${w.path} does not exist` });
1786
1864
  continue;
1787
1865
  }
@@ -2018,18 +2096,19 @@ function formatAge(iso) {
2018
2096
  const d = Math.floor(h / 24);
2019
2097
  return `${d}d`;
2020
2098
  }
2021
- var import_node_child_process8, import_node_fs10, import_node_path7, DEFAULT_ENV_FILE;
2099
+ var import_node_child_process8, import_node_fs11, import_node_path8, DEFAULT_ENV_FILE;
2022
2100
  var init_orchestrator = __esm({
2023
2101
  "src/server/orbit/orchestrator.ts"() {
2024
2102
  "use strict";
2025
2103
  import_node_child_process8 = require("node:child_process");
2026
- import_node_fs10 = require("node:fs");
2027
- import_node_path7 = require("node:path");
2104
+ import_node_fs11 = require("node:fs");
2105
+ import_node_path8 = require("node:path");
2028
2106
  init_adapter_registry();
2029
2107
  init_env_rewriter();
2030
2108
  init_gate_runner();
2031
2109
  init_manifest();
2032
2110
  init_registry();
2111
+ init_active();
2033
2112
  init_slug();
2034
2113
  DEFAULT_ENV_FILE = ".env.local";
2035
2114
  }
@@ -2093,11 +2172,16 @@ async function handleTool(name, args) {
2093
2172
  const result = switchTo({ branch, projectRoot });
2094
2173
  return text({
2095
2174
  slug: result.entry.slug,
2175
+ branch: result.entry.branch,
2176
+ anchor: result.anchor,
2096
2177
  path: result.path,
2097
2178
  envFile: result.envFile,
2098
- nextAction: `cd ${result.path}`
2179
+ nextAction: result.nextAction
2099
2180
  });
2100
2181
  }
2182
+ case "orbit.deactivate": {
2183
+ return text(deactivate({ projectRoot }));
2184
+ }
2101
2185
  case "orbit.drop": {
2102
2186
  const branch = String(args.branch ?? "");
2103
2187
  if (!branch) return text({ error: "branch is required" });
@@ -2256,15 +2340,20 @@ var init_orbit_mcp = __esm({
2256
2340
  },
2257
2341
  {
2258
2342
  name: "orbit.switch",
2259
- description: "Look up the path + env file of an existing orbit by branch name. Does NOT change any shell \u2014 the caller is expected to `cd` into the returned path. Useful for agents that need to operate inside an already-created orbit.",
2343
+ description: 'Activate an orbit by branch name. Atomically repoints the stable anchor <project>/.launchsecure/orbit/current at the orbit\'s worktree and records active.json, then returns `anchor` (the current/ path) + `nextAction`. AFTER calling this, operate on the anchor: Read/Edit/Write under `.launchsecure/orbit/current/<path>`, pass `project_root: ".launchsecure/orbit/current"` to chart, and run the returned `nextAction` (cd into current) so commands execute in the orbit. Switching to a different orbit just repoints the anchor; re-run nextAction afterwards.',
2260
2344
  inputSchema: {
2261
2345
  type: "object",
2262
2346
  properties: {
2263
- branch: { type: "string", description: "Branch name to look up." }
2347
+ branch: { type: "string", description: "Branch name of the orbit to activate." }
2264
2348
  },
2265
2349
  required: ["branch"]
2266
2350
  }
2267
2351
  },
2352
+ {
2353
+ name: "orbit.deactivate",
2354
+ description: "Clear the active-orbit anchor for this project \u2014 removes <project>/.launchsecure/orbit/current and active.json so no orbit is active. Use when you're done working in an orbit and want subsequent work to target the main checkout. No-op if nothing is active.",
2355
+ inputSchema: { type: "object", properties: {} }
2356
+ },
2268
2357
  {
2269
2358
  name: "orbit.drop",
2270
2359
  description: "Tear down an orbit: backup the database (default true), drop the database, terminate any process holding files inside the worktree (chart/beacon MCPs, dev servers \u2014 via lsof+SIGTERM), remove the worktree, sweep any leftover on-disk dir, deregister. Idempotent: safe to retry on a partial state (e.g. git already removed the worktree but the registry still claims it). Returns { slug, backups, freed }.",
@@ -2326,8 +2415,8 @@ var init_orbit_mcp = __esm({
2326
2415
  });
2327
2416
 
2328
2417
  // src/server/orbit-entry.ts
2329
- var import_node_fs11 = require("node:fs");
2330
- var import_node_path8 = require("node:path");
2418
+ var import_node_fs12 = require("node:fs");
2419
+ var import_node_path9 = require("node:path");
2331
2420
  init_orchestrator();
2332
2421
  init_logger();
2333
2422
  function parseFlags(argv) {
@@ -2413,14 +2502,22 @@ ${JSON.stringify(result, null, 2)}
2413
2502
  if (!branch) die("usage: launch-orbit switch <branch> [--emit-cd]");
2414
2503
  const result = switchTo({ branch, projectRoot });
2415
2504
  if (flags.emitCd) {
2416
- process.stdout.write(`cd ${result.path}
2505
+ process.stdout.write(`cd ${result.anchor}
2417
2506
  `);
2418
2507
  } else {
2419
- process.stdout.write(`${JSON.stringify({ path: result.path, envFile: result.envFile, nextAction: `cd ${result.path}` }, null, 2)}
2420
- `);
2508
+ process.stdout.write(
2509
+ `${JSON.stringify({ anchor: result.anchor, path: result.path, envFile: result.envFile, nextAction: result.nextAction }, null, 2)}
2510
+ `
2511
+ );
2421
2512
  }
2422
2513
  return;
2423
2514
  }
2515
+ case "deactivate": {
2516
+ const result = deactivate({ projectRoot });
2517
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
2518
+ `);
2519
+ return;
2520
+ }
2424
2521
  case "drop": {
2425
2522
  const branch = positional[0];
2426
2523
  if (!branch) die("usage: launch-orbit drop <branch> [--no-backup]");
@@ -2495,8 +2592,8 @@ ${JSON.stringify(result, null, 2)}
2495
2592
  }
2496
2593
  }
2497
2594
  function runInit(projectRoot) {
2498
- const path = (0, import_node_path8.join)(projectRoot, "orbit.json");
2499
- if ((0, import_node_fs11.existsSync)(path)) {
2595
+ const path = (0, import_node_path9.join)(projectRoot, "orbit.json");
2596
+ if ((0, import_node_fs12.existsSync)(path)) {
2500
2597
  process.stderr.write(`[launch-orbit] orbit.json already exists at ${path}
2501
2598
  `);
2502
2599
  process.exit(1);
@@ -2551,7 +2648,7 @@ function runInit(projectRoot) {
2551
2648
  full: { resources: ["db", "ports"] }
2552
2649
  }
2553
2650
  };
2554
- (0, import_node_fs11.writeFileSync)(path, JSON.stringify(starter, null, 2) + "\n", "utf-8");
2651
+ (0, import_node_fs12.writeFileSync)(path, JSON.stringify(starter, null, 2) + "\n", "utf-8");
2555
2652
  process.stdout.write(`\u2713 wrote ${path}
2556
2653
  `);
2557
2654
  }
@@ -152,17 +152,26 @@ var SHORTHANDS = {
152
152
  sequencer: { port: 3517, bin: "launch-sequencer", args: [] },
153
153
  chart: { port: 52819, bin: "launch-chart", args: ["serve"] },
154
154
  deck: { port: 52829, bin: "launch-deck", args: ["serve"] },
155
- council: { port: 52839, bin: "launch-council", args: ["serve"] }
155
+ council: { port: 52839, bin: "launch-council", args: ["serve"] },
156
+ // Claude web terminal — exposes a viewable/drivable `claude` session at
157
+ // `bot.<baseDomain>`. NOTE: no auth gate yet (tracked as a separate
158
+ // high-priority rover-security work item); ships behind a plain link first.
159
+ bot: { port: 52849, bin: "launch-bot", args: ["serve"] }
156
160
  };
157
161
  var DNS_NAME_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
158
162
  function defaultServices() {
159
163
  return [expandShorthand("radar")];
160
164
  }
161
165
  function expandShorthand(name) {
166
+ if (name === "preview") {
167
+ const raw = process.env.PREVIEW_PORT;
168
+ const port = raw && Number.isFinite(Number.parseInt(raw, 10)) ? Number.parseInt(raw, 10) : 3e3;
169
+ return { name: "preview", port, bin: "", args: [], skipSpawn: true };
170
+ }
162
171
  const def = SHORTHANDS[name];
163
172
  if (!def) {
164
173
  throw new Error(
165
- `[launch-kit-services] unknown shorthand "${name}" \u2014 known: ${Object.keys(SHORTHANDS).join(", ")}. Use an object entry { name, port, bin, args } for custom services.`
174
+ `[launch-kit-services] unknown shorthand "${name}" \u2014 known: ${[...Object.keys(SHORTHANDS), "preview"].join(", ")}. Use an object entry { name, port, bin, args } for custom services.`
166
175
  );
167
176
  }
168
177
  return { name, port: def.port, bin: def.bin, args: [...def.args] };
@@ -243,7 +252,7 @@ function resolveServices(opts = {}) {
243
252
  }
244
253
  return validate(defaultServices());
245
254
  }
246
- var SHORTHAND_NAMES = Object.keys(SHORTHANDS);
255
+ var SHORTHAND_NAMES = [...Object.keys(SHORTHANDS), "preview"];
247
256
 
248
257
  // src/server/cf-ingress.ts
249
258
  var import_node_fs2 = require("node:fs");
@@ -455,6 +464,23 @@ function setupClaudeCredentials() {
455
464
  cfg.lastOnboardingVersion = cfg.lastOnboardingVersion ?? "2.1.159";
456
465
  cfg.numStartups = (cfg.numStartups ?? 0) + 1;
457
466
  cfg.installMethod = cfg.installMethod ?? "global";
467
+ const PREAPPROVED_MCPS = [
468
+ "launch-secure",
469
+ "launch-chart",
470
+ "launch-deck",
471
+ "launch-orbit",
472
+ "launch-recall",
473
+ "launch-beacon",
474
+ "launch-sequencer"
475
+ ];
476
+ const projects = cfg.projects ?? {};
477
+ const wsKey = "/workspace";
478
+ const wsProject = projects[wsKey] ?? {};
479
+ const existingEnabled = Array.isArray(wsProject.enabledMcpjsonServers) ? wsProject.enabledMcpjsonServers : [];
480
+ const mergedEnabled = Array.from(/* @__PURE__ */ new Set([...existingEnabled, ...PREAPPROVED_MCPS]));
481
+ wsProject.enabledMcpjsonServers = mergedEnabled;
482
+ projects[wsKey] = wsProject;
483
+ cfg.projects = projects;
458
484
  (0, import_node_fs3.writeFileSync)(configPath, JSON.stringify(cfg, null, 2));
459
485
  (0, import_node_fs3.chmodSync)(configPath, 384);
460
486
  }
@@ -464,6 +490,27 @@ function setupGitAndGh() {
464
490
  const status = run("launch-kit", ["setup-git", `--identity=${name} <${email}>`]);
465
491
  if (status !== 0) fail(`[entrypoint] launch-kit setup-git failed (status ${status})`);
466
492
  }
493
+ function detectAndSetPreviewPort() {
494
+ if (process.env.PREVIEW_PORT) return;
495
+ try {
496
+ const pkgPath = "/workspace/package.json";
497
+ if (!(0, import_node_fs3.existsSync)(pkgPath)) return;
498
+ const pkg = JSON.parse((0, import_node_fs3.readFileSync)(pkgPath, "utf-8"));
499
+ const scripts = pkg.scripts ?? {};
500
+ const portRe = /(?:--port[= ]|-p\s+|\bPORT=)(\d{2,5})\b/;
501
+ for (const name of ["dev", "start", "serve"]) {
502
+ const script = scripts[name];
503
+ if (typeof script !== "string") continue;
504
+ const m = script.match(portRe);
505
+ if (m) {
506
+ process.env.PREVIEW_PORT = m[1];
507
+ console.log(`[entrypoint] preview port detected from package.json scripts.${name}: ${m[1]}`);
508
+ return;
509
+ }
510
+ }
511
+ } catch {
512
+ }
513
+ }
467
514
  function initWorkspaceIfEmpty() {
468
515
  process.chdir("/workspace");
469
516
  if ((0, import_node_fs3.existsSync)(".git")) {
@@ -563,6 +610,10 @@ function spawnServiceGroup(services) {
563
610
  let exitedCount = 0;
564
611
  let firstFailure = null;
565
612
  for (const spec of services) {
613
+ if (spec.skipSpawn) {
614
+ console.log(`[entrypoint] ${spec.name} \u2192 ingress-only on port ${spec.port} (no spawn; user starts dev server here)`);
615
+ continue;
616
+ }
566
617
  const args = [...spec.args, "--port", String(spec.port)];
567
618
  console.log(`[entrypoint] starting ${spec.name}: ${spec.bin} ${args.join(" ")}`);
568
619
  const proc = (0, import_node_child_process.spawn)(spec.bin, args, { stdio: ["ignore", "pipe", "pipe"] });
@@ -599,6 +650,7 @@ async function main() {
599
650
  setupClaudeCredentials();
600
651
  setupGitAndGh();
601
652
  initWorkspaceIfEmpty();
653
+ detectAndSetPreviewPort();
602
654
  let services;
603
655
  try {
604
656
  services = resolveServices();
File without changes
File without changes