@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.
- package/dist/server/cli.js +37 -9
- package/dist/server/council-entry.js +0 -0
- package/dist/server/fb-wizard.js +0 -0
- package/dist/server/init-entry.js +128 -20
- package/dist/server/launch-bot-entry.js +4078 -0
- package/dist/server/orbit-entry.js +123 -26
- package/dist/server/radar-docker-init-entry.js +55 -3
- package/dist/server/radar-entrypoint-entry.js +0 -0
- package/dist/server/radar-teardown-entry.js +0 -0
- package/dist/server/rover-entry.js +547 -114
- package/package.json +24 -24
- package/scaffolds/ls-marketplace/plugins/kit/skills/brief/SKILL.md +53 -22
- package/scaffolds/ls-marketplace/plugins/kit/skills/brief/briefs.mjs +152 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/kickoff/SKILL.md +151 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/orbit/SKILL.md +14 -2
- package/scaffolds/migrate-safety/scripts/migrate-with-backup.sh +0 -0
- package/scaffolds/recall-hook/scripts/ensure-recall.sh +0 -0
package/dist/server/cli.js
CHANGED
|
@@ -36689,6 +36689,7 @@ function parseArgs() {
|
|
|
36689
36689
|
const args = process.argv.slice(2);
|
|
36690
36690
|
let port = 0;
|
|
36691
36691
|
let token = null;
|
|
36692
|
+
let course = null;
|
|
36692
36693
|
const subcommand = args[0] && !args[0].startsWith("--") ? args[0] : null;
|
|
36693
36694
|
for (let i = 0; i < args.length; i++) {
|
|
36694
36695
|
if (args[i].startsWith("--token=")) {
|
|
@@ -36699,6 +36700,10 @@ function parseArgs() {
|
|
|
36699
36700
|
port = parseInt(args[i].slice("--port=".length), 10);
|
|
36700
36701
|
} else if (args[i] === "--port" && args[i + 1]) {
|
|
36701
36702
|
port = parseInt(args[++i], 10);
|
|
36703
|
+
} else if (args[i].startsWith("--course=")) {
|
|
36704
|
+
course = args[i].slice("--course=".length);
|
|
36705
|
+
} else if (args[i] === "--course" && args[i + 1]) {
|
|
36706
|
+
course = args[++i];
|
|
36702
36707
|
}
|
|
36703
36708
|
}
|
|
36704
36709
|
if (!port) {
|
|
@@ -36715,7 +36720,7 @@ function parseArgs() {
|
|
|
36715
36720
|
}
|
|
36716
36721
|
}
|
|
36717
36722
|
if (!port) port = 52718;
|
|
36718
|
-
return { port, token, serverUrl: LAUNCHSECURE_URL, subcommand };
|
|
36723
|
+
return { port, token, serverUrl: LAUNCHSECURE_URL, subcommand, course };
|
|
36719
36724
|
}
|
|
36720
36725
|
function tryListen(server, port, maxRetries = 10) {
|
|
36721
36726
|
return new Promise((resolve6, reject) => {
|
|
@@ -37286,7 +37291,7 @@ if (parsedArgs.subcommand === "mcp:graph") {
|
|
|
37286
37291
|
var __isMcpMode = parsedArgs.subcommand === "mcp:graph";
|
|
37287
37292
|
var __isRadarMode = parsedArgs.subcommand === "radar";
|
|
37288
37293
|
var radar = null;
|
|
37289
|
-
function readLaunchSecureMcpConfig() {
|
|
37294
|
+
function readLaunchSecureMcpConfig(courseOverride) {
|
|
37290
37295
|
const fix = `Run \`npx @launchsecure/launch-kit@latest refresh\` to re-sync this project (or \`init\` if you haven't bootstrapped yet).`;
|
|
37291
37296
|
const cred = readCredFile(REPO_ROOT);
|
|
37292
37297
|
if (!cred) {
|
|
@@ -37296,9 +37301,12 @@ function readLaunchSecureMcpConfig() {
|
|
|
37296
37301
|
if (!nested) {
|
|
37297
37302
|
throw new Error(`${CONFIG_FILENAME} is malformed or missing required fields (pat/orgSlug/projectSlug/serverUrl). ${fix}`);
|
|
37298
37303
|
}
|
|
37299
|
-
const
|
|
37304
|
+
const courseName = courseOverride || nested.active;
|
|
37305
|
+
const profile = nested.profiles[courseName];
|
|
37300
37306
|
if (!profile) {
|
|
37301
|
-
|
|
37307
|
+
const available = Object.keys(nested.profiles).join(", ") || "(none)";
|
|
37308
|
+
const why = courseOverride ? `course "${courseName}" (from --course) not found in profiles. Available: ${available}.` : `active course "${courseName}" not found in profiles. ${fix}`;
|
|
37309
|
+
throw new Error(`${CONFIG_FILENAME} ${why}`);
|
|
37302
37310
|
}
|
|
37303
37311
|
const { pat, orgSlug, projectSlug, serverUrl } = profile;
|
|
37304
37312
|
if (!pat || !pat.startsWith("ls_pat_")) {
|
|
@@ -37307,7 +37315,7 @@ function readLaunchSecureMcpConfig() {
|
|
|
37307
37315
|
if (!orgSlug || !projectSlug || !serverUrl) {
|
|
37308
37316
|
throw new Error(`${CONFIG_FILENAME} is missing required fields (orgSlug, projectSlug, serverUrl). ${fix}`);
|
|
37309
37317
|
}
|
|
37310
|
-
return { pat, orgSlug, projectSlug, serverUrl, source: `${CONFIG_FILENAME}[${
|
|
37318
|
+
return { pat, orgSlug, projectSlug, serverUrl, source: `${CONFIG_FILENAME}[${courseName}]`, course: courseName };
|
|
37311
37319
|
}
|
|
37312
37320
|
if (!__isMcpMode) {
|
|
37313
37321
|
let gracefulShutdown = function() {
|
|
@@ -37728,7 +37736,7 @@ if (!__isMcpMode) {
|
|
|
37728
37736
|
if (__isRadarMode) {
|
|
37729
37737
|
let cfg;
|
|
37730
37738
|
try {
|
|
37731
|
-
cfg = readLaunchSecureMcpConfig();
|
|
37739
|
+
cfg = readLaunchSecureMcpConfig(parsedArgs.course);
|
|
37732
37740
|
} catch (err2) {
|
|
37733
37741
|
console.error(`[radar] ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
37734
37742
|
process.exit(1);
|
|
@@ -37736,9 +37744,29 @@ if (!__isMcpMode) {
|
|
|
37736
37744
|
const actualPort2 = await tryListen(server, PORT);
|
|
37737
37745
|
PORT = actualPort2;
|
|
37738
37746
|
const localUrl = `http://127.0.0.1:${PORT}`;
|
|
37739
|
-
|
|
37740
|
-
|
|
37741
|
-
|
|
37747
|
+
const dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
37748
|
+
const bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
37749
|
+
const cyan = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
37750
|
+
const COURSE_LABELS = {
|
|
37751
|
+
prod: "Production",
|
|
37752
|
+
production: "Production",
|
|
37753
|
+
staging: "Staging",
|
|
37754
|
+
stage: "Staging",
|
|
37755
|
+
local: "Local",
|
|
37756
|
+
dev: "Development",
|
|
37757
|
+
development: "Development"
|
|
37758
|
+
};
|
|
37759
|
+
const courseLabel = COURSE_LABELS[cfg.course.toLowerCase()] ?? cfg.course.charAt(0).toUpperCase() + cfg.course.slice(1);
|
|
37760
|
+
const row = (label, value) => ` ${dim(label.padEnd(11))}${value}`;
|
|
37761
|
+
console.log("");
|
|
37762
|
+
console.log(` ${cyan("\u25C9")} ${bold("LaunchPod Radar")}`);
|
|
37763
|
+
console.log("");
|
|
37764
|
+
const courseTail = parsedArgs.course ? dim(`\xB7 ${cfg.course} \xB7 --course override`) : dim(`\xB7 ${cfg.course}`);
|
|
37765
|
+
console.log(row("Course", `${bold(courseLabel)} ${courseTail}`));
|
|
37766
|
+
console.log(row("Cloud", cfg.serverUrl));
|
|
37767
|
+
console.log(row("Project", `${cfg.orgSlug} ${dim("/")} ${cfg.projectSlug}`));
|
|
37768
|
+
console.log(row("Active on", localUrl));
|
|
37769
|
+
console.log("");
|
|
37742
37770
|
try {
|
|
37743
37771
|
radar = new Radar({
|
|
37744
37772
|
projectRoot: PROJECT_DIR,
|
|
File without changes
|
package/dist/server/fb-wizard.js
CHANGED
|
File without changes
|
|
@@ -473,10 +473,15 @@ function defaultServices() {
|
|
|
473
473
|
return [expandShorthand("radar")];
|
|
474
474
|
}
|
|
475
475
|
function expandShorthand(name) {
|
|
476
|
+
if (name === "preview") {
|
|
477
|
+
const raw = process.env.PREVIEW_PORT;
|
|
478
|
+
const port = raw && Number.isFinite(Number.parseInt(raw, 10)) ? Number.parseInt(raw, 10) : 3e3;
|
|
479
|
+
return { name: "preview", port, bin: "", args: [], skipSpawn: true };
|
|
480
|
+
}
|
|
476
481
|
const def = SHORTHANDS[name];
|
|
477
482
|
if (!def) {
|
|
478
483
|
throw new Error(
|
|
479
|
-
`[launch-kit-services] unknown shorthand "${name}" \u2014 known: ${Object.keys(SHORTHANDS).join(", ")}. Use an object entry { name, port, bin, args } for custom services.`
|
|
484
|
+
`[launch-kit-services] unknown shorthand "${name}" \u2014 known: ${[...Object.keys(SHORTHANDS), "preview"].join(", ")}. Use an object entry { name, port, bin, args } for custom services.`
|
|
480
485
|
);
|
|
481
486
|
}
|
|
482
487
|
return { name, port: def.port, bin: def.bin, args: [...def.args] };
|
|
@@ -568,10 +573,14 @@ var init_launch_kit_services = __esm({
|
|
|
568
573
|
sequencer: { port: 3517, bin: "launch-sequencer", args: [] },
|
|
569
574
|
chart: { port: 52819, bin: "launch-chart", args: ["serve"] },
|
|
570
575
|
deck: { port: 52829, bin: "launch-deck", args: ["serve"] },
|
|
571
|
-
council: { port: 52839, bin: "launch-council", args: ["serve"] }
|
|
576
|
+
council: { port: 52839, bin: "launch-council", args: ["serve"] },
|
|
577
|
+
// Claude web terminal — exposes a viewable/drivable `claude` session at
|
|
578
|
+
// `bot.<baseDomain>`. NOTE: no auth gate yet (tracked as a separate
|
|
579
|
+
// high-priority rover-security work item); ships behind a plain link first.
|
|
580
|
+
bot: { port: 52849, bin: "launch-bot", args: ["serve"] }
|
|
572
581
|
};
|
|
573
582
|
DNS_NAME_RE = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
574
|
-
SHORTHAND_NAMES = Object.keys(SHORTHANDS);
|
|
583
|
+
SHORTHAND_NAMES = [...Object.keys(SHORTHANDS), "preview"];
|
|
575
584
|
}
|
|
576
585
|
});
|
|
577
586
|
|
|
@@ -790,6 +799,23 @@ function setupClaudeCredentials() {
|
|
|
790
799
|
cfg.lastOnboardingVersion = cfg.lastOnboardingVersion ?? "2.1.159";
|
|
791
800
|
cfg.numStartups = (cfg.numStartups ?? 0) + 1;
|
|
792
801
|
cfg.installMethod = cfg.installMethod ?? "global";
|
|
802
|
+
const PREAPPROVED_MCPS = [
|
|
803
|
+
"launch-secure",
|
|
804
|
+
"launch-chart",
|
|
805
|
+
"launch-deck",
|
|
806
|
+
"launch-orbit",
|
|
807
|
+
"launch-recall",
|
|
808
|
+
"launch-beacon",
|
|
809
|
+
"launch-sequencer"
|
|
810
|
+
];
|
|
811
|
+
const projects = cfg.projects ?? {};
|
|
812
|
+
const wsKey = "/workspace";
|
|
813
|
+
const wsProject = projects[wsKey] ?? {};
|
|
814
|
+
const existingEnabled = Array.isArray(wsProject.enabledMcpjsonServers) ? wsProject.enabledMcpjsonServers : [];
|
|
815
|
+
const mergedEnabled = Array.from(/* @__PURE__ */ new Set([...existingEnabled, ...PREAPPROVED_MCPS]));
|
|
816
|
+
wsProject.enabledMcpjsonServers = mergedEnabled;
|
|
817
|
+
projects[wsKey] = wsProject;
|
|
818
|
+
cfg.projects = projects;
|
|
793
819
|
(0, import_node_fs3.writeFileSync)(configPath, JSON.stringify(cfg, null, 2));
|
|
794
820
|
(0, import_node_fs3.chmodSync)(configPath, 384);
|
|
795
821
|
}
|
|
@@ -799,6 +825,27 @@ function setupGitAndGh() {
|
|
|
799
825
|
const status = run2("launch-kit", ["setup-git", `--identity=${name} <${email}>`]);
|
|
800
826
|
if (status !== 0) fail(`[entrypoint] launch-kit setup-git failed (status ${status})`);
|
|
801
827
|
}
|
|
828
|
+
function detectAndSetPreviewPort() {
|
|
829
|
+
if (process.env.PREVIEW_PORT) return;
|
|
830
|
+
try {
|
|
831
|
+
const pkgPath = "/workspace/package.json";
|
|
832
|
+
if (!(0, import_node_fs3.existsSync)(pkgPath)) return;
|
|
833
|
+
const pkg = JSON.parse((0, import_node_fs3.readFileSync)(pkgPath, "utf-8"));
|
|
834
|
+
const scripts = pkg.scripts ?? {};
|
|
835
|
+
const portRe = /(?:--port[= ]|-p\s+|\bPORT=)(\d{2,5})\b/;
|
|
836
|
+
for (const name of ["dev", "start", "serve"]) {
|
|
837
|
+
const script = scripts[name];
|
|
838
|
+
if (typeof script !== "string") continue;
|
|
839
|
+
const m = script.match(portRe);
|
|
840
|
+
if (m) {
|
|
841
|
+
process.env.PREVIEW_PORT = m[1];
|
|
842
|
+
console.log(`[entrypoint] preview port detected from package.json scripts.${name}: ${m[1]}`);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
} catch {
|
|
847
|
+
}
|
|
848
|
+
}
|
|
802
849
|
function initWorkspaceIfEmpty() {
|
|
803
850
|
process.chdir("/workspace");
|
|
804
851
|
if ((0, import_node_fs3.existsSync)(".git")) {
|
|
@@ -898,6 +945,10 @@ function spawnServiceGroup(services) {
|
|
|
898
945
|
let exitedCount = 0;
|
|
899
946
|
let firstFailure = null;
|
|
900
947
|
for (const spec of services) {
|
|
948
|
+
if (spec.skipSpawn) {
|
|
949
|
+
console.log(`[entrypoint] ${spec.name} \u2192 ingress-only on port ${spec.port} (no spawn; user starts dev server here)`);
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
901
952
|
const args = [...spec.args, "--port", String(spec.port)];
|
|
902
953
|
console.log(`[entrypoint] starting ${spec.name}: ${spec.bin} ${args.join(" ")}`);
|
|
903
954
|
const proc = (0, import_node_child_process2.spawn)(spec.bin, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -934,6 +985,7 @@ async function main() {
|
|
|
934
985
|
setupClaudeCredentials();
|
|
935
986
|
setupGitAndGh();
|
|
936
987
|
initWorkspaceIfEmpty();
|
|
988
|
+
detectAndSetPreviewPort();
|
|
937
989
|
let services;
|
|
938
990
|
try {
|
|
939
991
|
services = resolveServices();
|
|
@@ -1007,10 +1059,28 @@ var import_node_child_process = require("node:child_process");
|
|
|
1007
1059
|
function run(cmd, args, stdio = "inherit") {
|
|
1008
1060
|
return (0, import_node_child_process.spawnSync)(cmd, args, { stdio }).status ?? 1;
|
|
1009
1061
|
}
|
|
1010
|
-
function
|
|
1011
|
-
|
|
1062
|
+
function hasGh() {
|
|
1063
|
+
return (0, import_node_child_process.spawnSync)("gh", ["--version"], { stdio: "ignore" }).status === 0;
|
|
1064
|
+
}
|
|
1065
|
+
function wireGitHubAuth() {
|
|
1066
|
+
if (!process.env.GH_TOKEN) return false;
|
|
1067
|
+
if (hasGh()) {
|
|
1012
1068
|
run("gh", ["auth", "setup-git"]);
|
|
1069
|
+
return true;
|
|
1013
1070
|
}
|
|
1071
|
+
const key = "credential.https://github.com.helper";
|
|
1072
|
+
run("git", ["config", "--global", "--replace-all", key, ""]);
|
|
1073
|
+
run("git", [
|
|
1074
|
+
"config",
|
|
1075
|
+
"--global",
|
|
1076
|
+
"--add",
|
|
1077
|
+
key,
|
|
1078
|
+
`!f() { echo username=x-access-token; echo "password=$GH_TOKEN"; }; f`
|
|
1079
|
+
]);
|
|
1080
|
+
return true;
|
|
1081
|
+
}
|
|
1082
|
+
function configureGitForBot(identity) {
|
|
1083
|
+
wireGitHubAuth();
|
|
1014
1084
|
run("git", ["config", "--global", "user.name", identity.name]);
|
|
1015
1085
|
run("git", ["config", "--global", "user.email", identity.email]);
|
|
1016
1086
|
run("git", ["config", "--global", "init.defaultBranch", "main"]);
|
|
@@ -1389,9 +1459,23 @@ What it does:
|
|
|
1389
1459
|
Does NOT clone, re-install deps, re-prompt for PAT, or re-run the onboard
|
|
1390
1460
|
script. Use \`init\` for those.
|
|
1391
1461
|
|
|
1462
|
+
No cred yet? If the repo already exists here but was never init'd (e.g. the
|
|
1463
|
+
repo was created by hand and connected in LS afterward), pass
|
|
1464
|
+
--token/--org/--project and refresh will SEED the cred file, then proceed \u2014
|
|
1465
|
+
no clone, no init required.
|
|
1466
|
+
|
|
1392
1467
|
Options:
|
|
1393
1468
|
--dir=<path> Target directory (default: cwd). Must contain a
|
|
1394
|
-
valid .launch-secure.cred.config
|
|
1469
|
+
valid .launch-secure.cred.config, OR pass the
|
|
1470
|
+
--token/--org/--project flags below to seed one.
|
|
1471
|
+
--token=<ls_pat_\u2026> LaunchSecure PAT. Only needed to seed a cred when
|
|
1472
|
+
none exists yet (otherwise read from the cred file).
|
|
1473
|
+
--org=<orgSlug> Org slug for the seeded cred (with --token).
|
|
1474
|
+
--project=<projectSlug> Project slug for the seeded cred (with --token).
|
|
1475
|
+
--url=<serverUrl> LaunchSecure base URL for the seeded cred
|
|
1476
|
+
(default: ${DEFAULT_SERVER_URL}).
|
|
1477
|
+
--course=<name> Course/profile name for the seeded cred
|
|
1478
|
+
(default: inferred from --url).
|
|
1395
1479
|
--no-migrate-safety Skip refreshing the migrate-safety scaffold.
|
|
1396
1480
|
--no-ls-marketplace Skip refreshing the launch-secure marketplace.
|
|
1397
1481
|
--no-recall-hook Skip refreshing the recall-hook scaffold.
|
|
@@ -1453,9 +1537,13 @@ Options:
|
|
|
1453
1537
|
--git-identity="N <e>" Non-interactive git identity for service-account /
|
|
1454
1538
|
CI / Docker runs. Configures git user.name, user.email,
|
|
1455
1539
|
init.defaultBranch=main, pull.rebase=false; also
|
|
1456
|
-
wires GH_TOKEN into git's credential helper via
|
|
1457
|
-
\`gh auth setup-git
|
|
1540
|
+
wires GH_TOKEN into git's credential helper (via
|
|
1541
|
+
\`gh auth setup-git\`, or a gh-less helper when the
|
|
1542
|
+
GitHub CLI is absent) when GH_TOKEN is set. Example:
|
|
1458
1543
|
--git-identity="Radar Bot <radar@launchpod.local>".
|
|
1544
|
+
Note: GH_TOKEN is wired for the clone even without
|
|
1545
|
+
--git-identity, so private-repo clones work on hosts
|
|
1546
|
+
with no \`gh\` as long as GH_TOKEN is set.
|
|
1459
1547
|
--no-install Skip dependency install step (also skips the onboard
|
|
1460
1548
|
script \u2014 install is its prerequisite).
|
|
1461
1549
|
--no-onboard Skip the onboard script even when install runs.
|
|
@@ -1557,9 +1645,9 @@ function preflight() {
|
|
|
1557
1645
|
const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
|
|
1558
1646
|
if (nodeMajor < 18) fail2(`Node.js >= 18 required (current: ${process.versions.node}).`);
|
|
1559
1647
|
if (!which("git")) fail2("git not found in PATH. Install git: https://git-scm.com/downloads");
|
|
1560
|
-
const
|
|
1561
|
-
ok(`preflight ok \u2014 node ${process.versions.node}, git present${
|
|
1562
|
-
return { hasGh };
|
|
1648
|
+
const hasGh2 = which("gh") !== null;
|
|
1649
|
+
ok(`preflight ok \u2014 node ${process.versions.node}, git present${hasGh2 ? ", gh present" : ", gh not found (will use git for clone)"}`);
|
|
1650
|
+
return { hasGh: hasGh2 };
|
|
1563
1651
|
}
|
|
1564
1652
|
var PROJECT_INFO_TIMEOUT_MS = 3e4;
|
|
1565
1653
|
var PROJECT_INFO_MAX_ATTEMPTS = 3;
|
|
@@ -1712,11 +1800,11 @@ function dirIsEmpty(dir) {
|
|
|
1712
1800
|
if (!fs5.existsSync(dir)) return true;
|
|
1713
1801
|
return fs5.readdirSync(dir).length === 0;
|
|
1714
1802
|
}
|
|
1715
|
-
function cloneRepo(repoUrl, targetDir,
|
|
1803
|
+
function cloneRepo(repoUrl, targetDir, hasGh2) {
|
|
1716
1804
|
const isGithub = /github\.com/i.test(repoUrl);
|
|
1717
1805
|
let cmd;
|
|
1718
1806
|
let args;
|
|
1719
|
-
if (
|
|
1807
|
+
if (hasGh2 && isGithub) {
|
|
1720
1808
|
cmd = "gh";
|
|
1721
1809
|
args = ["repo", "clone", repoUrl, targetDir];
|
|
1722
1810
|
info(`cloning via gh: ${repoUrl} \u2192 ${targetDir}`);
|
|
@@ -1732,7 +1820,7 @@ function cloneRepo(repoUrl, targetDir, hasGh) {
|
|
|
1732
1820
|
const res = (0, import_node_child_process3.spawnSync)(cmd, args, { stdio: "inherit" });
|
|
1733
1821
|
if (res.status !== 0) {
|
|
1734
1822
|
fail2(
|
|
1735
|
-
`Clone failed (${cmd} exited ${res.status}). For private repos make sure your GitHub auth is set up: \`gh auth login
|
|
1823
|
+
`Clone failed (${cmd} exited ${res.status}). For private repos make sure your GitHub auth is set up: \`gh auth login\`, set GH_TOKEN=<a PAT with repo scope> before re-running (works without the GitHub CLI), or add an SSH key to your GitHub account.`
|
|
1736
1824
|
);
|
|
1737
1825
|
}
|
|
1738
1826
|
if (!fs5.existsSync(path5.join(targetDir, ".git"))) {
|
|
@@ -2383,9 +2471,26 @@ async function mainRefresh(args) {
|
|
|
2383
2471
|
ok(`wrote ${CONFIG_FILENAME} (course: ${courseName})`);
|
|
2384
2472
|
}
|
|
2385
2473
|
}
|
|
2474
|
+
if (!cred && args.token && args.orgSlug && args.projectSlug) {
|
|
2475
|
+
const courseName = args.course ?? inferCourseName(args.serverUrl);
|
|
2476
|
+
const seedCfg = {
|
|
2477
|
+
pat: args.token,
|
|
2478
|
+
orgSlug: args.orgSlug,
|
|
2479
|
+
projectSlug: args.projectSlug,
|
|
2480
|
+
serverUrl: args.serverUrl
|
|
2481
|
+
};
|
|
2482
|
+
info(`no ${CONFIG_FILENAME} found \u2014 seeding it from --token/--org/--project (course: ${courseName})`);
|
|
2483
|
+
writeConfigFile(targetDir, seedCfg, courseName);
|
|
2484
|
+
cred = { active: courseName, profiles: { [courseName]: seedCfg } };
|
|
2485
|
+
}
|
|
2386
2486
|
if (!cred) {
|
|
2387
2487
|
fail2(
|
|
2388
|
-
`no ${CONFIG_FILENAME} found at ${targetDir}, and could not recover from .mcp.json.
|
|
2488
|
+
`no ${CONFIG_FILENAME} found at ${targetDir}, and could not recover from .mcp.json.
|
|
2489
|
+
Refresh re-applies configs to an already-wired checkout, so it needs a cred. Two ways forward:
|
|
2490
|
+
\u2022 Seed it inline (you already have the repo): re-run refresh with credentials \u2014
|
|
2491
|
+
launch-kit refresh --dir=${path5.relative(cwd, targetDir) || "."} --token=<pat> --org=<org> --project=<project>
|
|
2492
|
+
\u2022 Or run a full init in place \u2014 it detects this existing repo and skips the clone:
|
|
2493
|
+
launch-kit init --token=<pat> --org=<org> --project=<project> --dir=${path5.relative(cwd, targetDir) || "."}`
|
|
2389
2494
|
);
|
|
2390
2495
|
}
|
|
2391
2496
|
const nested = toNested(cred);
|
|
@@ -2471,11 +2576,13 @@ async function mainInit(args) {
|
|
|
2471
2576
|
["project", args.projectSlug],
|
|
2472
2577
|
["server", args.serverUrl]
|
|
2473
2578
|
]);
|
|
2474
|
-
const { hasGh } = preflight();
|
|
2475
|
-
phase("preflight", { status: "ok", summary: `node ${process.versions.node}${
|
|
2579
|
+
const { hasGh: hasGh2 } = preflight();
|
|
2580
|
+
phase("preflight", { status: "ok", summary: `node ${process.versions.node}${hasGh2 ? " \xB7 git \xB7 gh" : " \xB7 git (gh not found \u2014 will use git for clone)"}` });
|
|
2476
2581
|
if (args.gitIdentity) {
|
|
2477
2582
|
configureGitForBot(args.gitIdentity);
|
|
2478
|
-
phase("git identity", { status: "ok", summary: `${args.gitIdentity.name} <${args.gitIdentity.email}>${process.env.GH_TOKEN ? " \xB7
|
|
2583
|
+
phase("git identity", { status: "ok", summary: `${args.gitIdentity.name} <${args.gitIdentity.email}>${process.env.GH_TOKEN ? " \xB7 git credential helper wired" : ""}` });
|
|
2584
|
+
} else if (wireGitHubAuth()) {
|
|
2585
|
+
phase("git auth", { status: "ok", summary: hasGh2 ? "GH_TOKEN \u2192 gh credential helper" : "GH_TOKEN \u2192 git credential helper (gh not found)" });
|
|
2479
2586
|
}
|
|
2480
2587
|
info(`resolving project ${args.orgSlug}/${args.projectSlug} on ${args.serverUrl} \u2026`);
|
|
2481
2588
|
let resolved;
|
|
@@ -2500,7 +2607,8 @@ async function mainInit(args) {
|
|
|
2500
2607
|
if (isGitRepo(targetDir)) {
|
|
2501
2608
|
const existingRemote = gitRemoteUrl(targetDir);
|
|
2502
2609
|
if (existingRemote && normalizeRepoUrl(existingRemote) === normalizedRemote) {
|
|
2503
|
-
ok(`${targetDir} is already a clone of ${repoUrl} \u2014 skipping clone,
|
|
2610
|
+
ok(`${targetDir} is already a clone of ${repoUrl} \u2014 wiring this existing repo (skipping clone, no GitHub auth needed)`);
|
|
2611
|
+
info(`tip: once wired, use \`launch-kit refresh\` here to re-apply configs without re-running init`);
|
|
2504
2612
|
skipClone = true;
|
|
2505
2613
|
} else {
|
|
2506
2614
|
fail2(`${targetDir} is a git repo but its remote (${existingRemote ?? "unknown"}) does not match ${repoUrl}. Refusing to overwrite. Pass --dir=<other-path>.`);
|
|
@@ -2512,7 +2620,7 @@ async function mainInit(args) {
|
|
|
2512
2620
|
const relTarget = path5.relative(cwd, targetDir) || ".";
|
|
2513
2621
|
if (!skipClone) {
|
|
2514
2622
|
section(`Cloning ${repoUrl}`);
|
|
2515
|
-
cloneRepo(repoUrl, targetDir,
|
|
2623
|
+
cloneRepo(repoUrl, targetDir, hasGh2);
|
|
2516
2624
|
phase("clone", { status: "ok", summary: `\u2192 ${relTarget}` });
|
|
2517
2625
|
} else {
|
|
2518
2626
|
phase("clone", { status: "in-sync", summary: `${relTarget} (already a clone of this repo)` });
|