@launchmatic/cli 0.3.1 → 0.6.0
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/README.md +178 -0
- package/dist/c.js +0 -0
- package/dist/index.js +1376 -128
- package/package.json +10 -1
package/dist/index.js
CHANGED
|
@@ -972,7 +972,7 @@ var require_command = __commonJS({
|
|
|
972
972
|
"use strict";
|
|
973
973
|
var EventEmitter = __require("events").EventEmitter;
|
|
974
974
|
var childProcess2 = __require("child_process");
|
|
975
|
-
var
|
|
975
|
+
var path7 = __require("path");
|
|
976
976
|
var fs8 = __require("fs");
|
|
977
977
|
var process22 = __require("process");
|
|
978
978
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
@@ -1905,9 +1905,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1905
1905
|
let launchWithNode = false;
|
|
1906
1906
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1907
1907
|
function findFile(baseDir, baseName) {
|
|
1908
|
-
const localBin =
|
|
1908
|
+
const localBin = path7.resolve(baseDir, baseName);
|
|
1909
1909
|
if (fs8.existsSync(localBin)) return localBin;
|
|
1910
|
-
if (sourceExt.includes(
|
|
1910
|
+
if (sourceExt.includes(path7.extname(baseName))) return void 0;
|
|
1911
1911
|
const foundExt = sourceExt.find(
|
|
1912
1912
|
(ext) => fs8.existsSync(`${localBin}${ext}`)
|
|
1913
1913
|
);
|
|
@@ -1925,17 +1925,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1925
1925
|
} catch (err) {
|
|
1926
1926
|
resolvedScriptPath = this._scriptPath;
|
|
1927
1927
|
}
|
|
1928
|
-
executableDir =
|
|
1929
|
-
|
|
1928
|
+
executableDir = path7.resolve(
|
|
1929
|
+
path7.dirname(resolvedScriptPath),
|
|
1930
1930
|
executableDir
|
|
1931
1931
|
);
|
|
1932
1932
|
}
|
|
1933
1933
|
if (executableDir) {
|
|
1934
1934
|
let localFile = findFile(executableDir, executableFile);
|
|
1935
1935
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
1936
|
-
const legacyName =
|
|
1936
|
+
const legacyName = path7.basename(
|
|
1937
1937
|
this._scriptPath,
|
|
1938
|
-
|
|
1938
|
+
path7.extname(this._scriptPath)
|
|
1939
1939
|
);
|
|
1940
1940
|
if (legacyName !== this._name) {
|
|
1941
1941
|
localFile = findFile(
|
|
@@ -1946,7 +1946,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1946
1946
|
}
|
|
1947
1947
|
executableFile = localFile || executableFile;
|
|
1948
1948
|
}
|
|
1949
|
-
launchWithNode = sourceExt.includes(
|
|
1949
|
+
launchWithNode = sourceExt.includes(path7.extname(executableFile));
|
|
1950
1950
|
let proc;
|
|
1951
1951
|
if (process22.platform !== "win32") {
|
|
1952
1952
|
if (launchWithNode) {
|
|
@@ -2786,7 +2786,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2786
2786
|
* @return {Command}
|
|
2787
2787
|
*/
|
|
2788
2788
|
nameFromFilename(filename) {
|
|
2789
|
-
this._name =
|
|
2789
|
+
this._name = path7.basename(filename, path7.extname(filename));
|
|
2790
2790
|
return this;
|
|
2791
2791
|
}
|
|
2792
2792
|
/**
|
|
@@ -2800,9 +2800,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2800
2800
|
* @param {string} [path]
|
|
2801
2801
|
* @return {(string|null|Command)}
|
|
2802
2802
|
*/
|
|
2803
|
-
executableDir(
|
|
2804
|
-
if (
|
|
2805
|
-
this._executableDir =
|
|
2803
|
+
executableDir(path8) {
|
|
2804
|
+
if (path8 === void 0) return this._executableDir;
|
|
2805
|
+
this._executableDir = path8;
|
|
2806
2806
|
return this;
|
|
2807
2807
|
}
|
|
2808
2808
|
/**
|
|
@@ -5797,16 +5797,16 @@ var require_validate = __commonJS({
|
|
|
5797
5797
|
const matches = RELATIVE_JSON_POINTER.exec($data);
|
|
5798
5798
|
if (!matches)
|
|
5799
5799
|
throw new Error(`Invalid JSON-pointer: ${$data}`);
|
|
5800
|
-
const
|
|
5800
|
+
const up2 = +matches[1];
|
|
5801
5801
|
jsonPointer = matches[2];
|
|
5802
5802
|
if (jsonPointer === "#") {
|
|
5803
|
-
if (
|
|
5804
|
-
throw new Error(errorMsg("property/index",
|
|
5805
|
-
return dataPathArr[dataLevel -
|
|
5803
|
+
if (up2 >= dataLevel)
|
|
5804
|
+
throw new Error(errorMsg("property/index", up2));
|
|
5805
|
+
return dataPathArr[dataLevel - up2];
|
|
5806
5806
|
}
|
|
5807
|
-
if (
|
|
5808
|
-
throw new Error(errorMsg("data",
|
|
5809
|
-
data = dataNames[dataLevel -
|
|
5807
|
+
if (up2 > dataLevel)
|
|
5808
|
+
throw new Error(errorMsg("data", up2));
|
|
5809
|
+
data = dataNames[dataLevel - up2];
|
|
5810
5810
|
if (!jsonPointer)
|
|
5811
5811
|
return data;
|
|
5812
5812
|
}
|
|
@@ -5819,8 +5819,8 @@ var require_validate = __commonJS({
|
|
|
5819
5819
|
}
|
|
5820
5820
|
}
|
|
5821
5821
|
return expr;
|
|
5822
|
-
function errorMsg(pointerType,
|
|
5823
|
-
return `Cannot access ${pointerType} ${
|
|
5822
|
+
function errorMsg(pointerType, up2) {
|
|
5823
|
+
return `Cannot access ${pointerType} ${up2} levels up, current level is ${dataLevel}`;
|
|
5824
5824
|
}
|
|
5825
5825
|
}
|
|
5826
5826
|
exports.getData = getData;
|
|
@@ -5984,7 +5984,7 @@ var require_compile = __commonJS({
|
|
|
5984
5984
|
const schOrFunc = root.refs[ref];
|
|
5985
5985
|
if (schOrFunc)
|
|
5986
5986
|
return schOrFunc;
|
|
5987
|
-
let _sch =
|
|
5987
|
+
let _sch = resolve5.call(this, root, ref);
|
|
5988
5988
|
if (_sch === void 0) {
|
|
5989
5989
|
const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
|
|
5990
5990
|
const { schemaId } = this.opts;
|
|
@@ -6011,7 +6011,7 @@ var require_compile = __commonJS({
|
|
|
6011
6011
|
function sameSchemaEnv(s1, s2) {
|
|
6012
6012
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
6013
6013
|
}
|
|
6014
|
-
function
|
|
6014
|
+
function resolve5(root, ref) {
|
|
6015
6015
|
let sch;
|
|
6016
6016
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
6017
6017
|
ref = sch;
|
|
@@ -6226,8 +6226,8 @@ var require_utils = __commonJS({
|
|
|
6226
6226
|
}
|
|
6227
6227
|
return ind;
|
|
6228
6228
|
}
|
|
6229
|
-
function removeDotSegments(
|
|
6230
|
-
let input =
|
|
6229
|
+
function removeDotSegments(path7) {
|
|
6230
|
+
let input = path7;
|
|
6231
6231
|
const output = [];
|
|
6232
6232
|
let nextSlash = -1;
|
|
6233
6233
|
let len = 0;
|
|
@@ -6426,8 +6426,8 @@ var require_schemes = __commonJS({
|
|
|
6426
6426
|
wsComponent.secure = void 0;
|
|
6427
6427
|
}
|
|
6428
6428
|
if (wsComponent.resourceName) {
|
|
6429
|
-
const [
|
|
6430
|
-
wsComponent.path =
|
|
6429
|
+
const [path7, query] = wsComponent.resourceName.split("?");
|
|
6430
|
+
wsComponent.path = path7 && path7 !== "/" ? path7 : void 0;
|
|
6431
6431
|
wsComponent.query = query;
|
|
6432
6432
|
wsComponent.resourceName = void 0;
|
|
6433
6433
|
}
|
|
@@ -6586,55 +6586,55 @@ var require_fast_uri = __commonJS({
|
|
|
6586
6586
|
}
|
|
6587
6587
|
return uri;
|
|
6588
6588
|
}
|
|
6589
|
-
function
|
|
6589
|
+
function resolve5(baseURI, relativeURI, options) {
|
|
6590
6590
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
6591
6591
|
const resolved = resolveComponent(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
6592
6592
|
schemelessOptions.skipEscape = true;
|
|
6593
6593
|
return serialize(resolved, schemelessOptions);
|
|
6594
6594
|
}
|
|
6595
|
-
function resolveComponent(base,
|
|
6595
|
+
function resolveComponent(base, relative3, options, skipNormalization) {
|
|
6596
6596
|
const target = {};
|
|
6597
6597
|
if (!skipNormalization) {
|
|
6598
6598
|
base = parse(serialize(base, options), options);
|
|
6599
|
-
|
|
6599
|
+
relative3 = parse(serialize(relative3, options), options);
|
|
6600
6600
|
}
|
|
6601
6601
|
options = options || {};
|
|
6602
|
-
if (!options.tolerant &&
|
|
6603
|
-
target.scheme =
|
|
6604
|
-
target.userinfo =
|
|
6605
|
-
target.host =
|
|
6606
|
-
target.port =
|
|
6607
|
-
target.path = removeDotSegments(
|
|
6608
|
-
target.query =
|
|
6602
|
+
if (!options.tolerant && relative3.scheme) {
|
|
6603
|
+
target.scheme = relative3.scheme;
|
|
6604
|
+
target.userinfo = relative3.userinfo;
|
|
6605
|
+
target.host = relative3.host;
|
|
6606
|
+
target.port = relative3.port;
|
|
6607
|
+
target.path = removeDotSegments(relative3.path || "");
|
|
6608
|
+
target.query = relative3.query;
|
|
6609
6609
|
} else {
|
|
6610
|
-
if (
|
|
6611
|
-
target.userinfo =
|
|
6612
|
-
target.host =
|
|
6613
|
-
target.port =
|
|
6614
|
-
target.path = removeDotSegments(
|
|
6615
|
-
target.query =
|
|
6610
|
+
if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
|
|
6611
|
+
target.userinfo = relative3.userinfo;
|
|
6612
|
+
target.host = relative3.host;
|
|
6613
|
+
target.port = relative3.port;
|
|
6614
|
+
target.path = removeDotSegments(relative3.path || "");
|
|
6615
|
+
target.query = relative3.query;
|
|
6616
6616
|
} else {
|
|
6617
|
-
if (!
|
|
6617
|
+
if (!relative3.path) {
|
|
6618
6618
|
target.path = base.path;
|
|
6619
|
-
if (
|
|
6620
|
-
target.query =
|
|
6619
|
+
if (relative3.query !== void 0) {
|
|
6620
|
+
target.query = relative3.query;
|
|
6621
6621
|
} else {
|
|
6622
6622
|
target.query = base.query;
|
|
6623
6623
|
}
|
|
6624
6624
|
} else {
|
|
6625
|
-
if (
|
|
6626
|
-
target.path = removeDotSegments(
|
|
6625
|
+
if (relative3.path[0] === "/") {
|
|
6626
|
+
target.path = removeDotSegments(relative3.path);
|
|
6627
6627
|
} else {
|
|
6628
6628
|
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
|
|
6629
|
-
target.path = "/" +
|
|
6629
|
+
target.path = "/" + relative3.path;
|
|
6630
6630
|
} else if (!base.path) {
|
|
6631
|
-
target.path =
|
|
6631
|
+
target.path = relative3.path;
|
|
6632
6632
|
} else {
|
|
6633
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
6633
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
|
|
6634
6634
|
}
|
|
6635
6635
|
target.path = removeDotSegments(target.path);
|
|
6636
6636
|
}
|
|
6637
|
-
target.query =
|
|
6637
|
+
target.query = relative3.query;
|
|
6638
6638
|
}
|
|
6639
6639
|
target.userinfo = base.userinfo;
|
|
6640
6640
|
target.host = base.host;
|
|
@@ -6642,7 +6642,7 @@ var require_fast_uri = __commonJS({
|
|
|
6642
6642
|
}
|
|
6643
6643
|
target.scheme = base.scheme;
|
|
6644
6644
|
}
|
|
6645
|
-
target.fragment =
|
|
6645
|
+
target.fragment = relative3.fragment;
|
|
6646
6646
|
return target;
|
|
6647
6647
|
}
|
|
6648
6648
|
function equal(uriA, uriB, options) {
|
|
@@ -6813,7 +6813,7 @@ var require_fast_uri = __commonJS({
|
|
|
6813
6813
|
var fastUri = {
|
|
6814
6814
|
SCHEMES,
|
|
6815
6815
|
normalize,
|
|
6816
|
-
resolve:
|
|
6816
|
+
resolve: resolve5,
|
|
6817
6817
|
resolveComponent,
|
|
6818
6818
|
equal,
|
|
6819
6819
|
serialize,
|
|
@@ -18766,14 +18766,14 @@ var baseOpen = async (options) => {
|
|
|
18766
18766
|
}
|
|
18767
18767
|
const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
|
|
18768
18768
|
if (options.wait) {
|
|
18769
|
-
return new Promise((
|
|
18769
|
+
return new Promise((resolve5, reject) => {
|
|
18770
18770
|
subprocess.once("error", reject);
|
|
18771
18771
|
subprocess.once("close", (exitCode) => {
|
|
18772
18772
|
if (!options.allowNonzeroExitCode && exitCode > 0) {
|
|
18773
18773
|
reject(new Error(`Exited with code ${exitCode}`));
|
|
18774
18774
|
return;
|
|
18775
18775
|
}
|
|
18776
|
-
|
|
18776
|
+
resolve5(subprocess);
|
|
18777
18777
|
});
|
|
18778
18778
|
});
|
|
18779
18779
|
}
|
|
@@ -18866,12 +18866,12 @@ var disallowedKeys = /* @__PURE__ */ new Set([
|
|
|
18866
18866
|
"constructor"
|
|
18867
18867
|
]);
|
|
18868
18868
|
var digits = new Set("0123456789");
|
|
18869
|
-
function getPathSegments(
|
|
18869
|
+
function getPathSegments(path7) {
|
|
18870
18870
|
const parts = [];
|
|
18871
18871
|
let currentSegment = "";
|
|
18872
18872
|
let currentPart = "start";
|
|
18873
18873
|
let isIgnoring = false;
|
|
18874
|
-
for (const character of
|
|
18874
|
+
for (const character of path7) {
|
|
18875
18875
|
switch (character) {
|
|
18876
18876
|
case "\\": {
|
|
18877
18877
|
if (currentPart === "index") {
|
|
@@ -18993,11 +18993,11 @@ function assertNotStringIndex(object, key) {
|
|
|
18993
18993
|
throw new Error("Cannot use string index");
|
|
18994
18994
|
}
|
|
18995
18995
|
}
|
|
18996
|
-
function getProperty(object,
|
|
18997
|
-
if (!isObject(object) || typeof
|
|
18996
|
+
function getProperty(object, path7, value) {
|
|
18997
|
+
if (!isObject(object) || typeof path7 !== "string") {
|
|
18998
18998
|
return value === void 0 ? object : value;
|
|
18999
18999
|
}
|
|
19000
|
-
const pathArray = getPathSegments(
|
|
19000
|
+
const pathArray = getPathSegments(path7);
|
|
19001
19001
|
if (pathArray.length === 0) {
|
|
19002
19002
|
return value;
|
|
19003
19003
|
}
|
|
@@ -19017,12 +19017,12 @@ function getProperty(object, path6, value) {
|
|
|
19017
19017
|
}
|
|
19018
19018
|
return object === void 0 ? value : object;
|
|
19019
19019
|
}
|
|
19020
|
-
function setProperty(object,
|
|
19021
|
-
if (!isObject(object) || typeof
|
|
19020
|
+
function setProperty(object, path7, value) {
|
|
19021
|
+
if (!isObject(object) || typeof path7 !== "string") {
|
|
19022
19022
|
return object;
|
|
19023
19023
|
}
|
|
19024
19024
|
const root = object;
|
|
19025
|
-
const pathArray = getPathSegments(
|
|
19025
|
+
const pathArray = getPathSegments(path7);
|
|
19026
19026
|
for (let index = 0; index < pathArray.length; index++) {
|
|
19027
19027
|
const key = pathArray[index];
|
|
19028
19028
|
assertNotStringIndex(object, key);
|
|
@@ -19035,11 +19035,11 @@ function setProperty(object, path6, value) {
|
|
|
19035
19035
|
}
|
|
19036
19036
|
return root;
|
|
19037
19037
|
}
|
|
19038
|
-
function deleteProperty(object,
|
|
19039
|
-
if (!isObject(object) || typeof
|
|
19038
|
+
function deleteProperty(object, path7) {
|
|
19039
|
+
if (!isObject(object) || typeof path7 !== "string") {
|
|
19040
19040
|
return false;
|
|
19041
19041
|
}
|
|
19042
|
-
const pathArray = getPathSegments(
|
|
19042
|
+
const pathArray = getPathSegments(path7);
|
|
19043
19043
|
for (let index = 0; index < pathArray.length; index++) {
|
|
19044
19044
|
const key = pathArray[index];
|
|
19045
19045
|
assertNotStringIndex(object, key);
|
|
@@ -19053,11 +19053,11 @@ function deleteProperty(object, path6) {
|
|
|
19053
19053
|
}
|
|
19054
19054
|
}
|
|
19055
19055
|
}
|
|
19056
|
-
function hasProperty(object,
|
|
19057
|
-
if (!isObject(object) || typeof
|
|
19056
|
+
function hasProperty(object, path7) {
|
|
19057
|
+
if (!isObject(object) || typeof path7 !== "string") {
|
|
19058
19058
|
return false;
|
|
19059
19059
|
}
|
|
19060
|
-
const pathArray = getPathSegments(
|
|
19060
|
+
const pathArray = getPathSegments(path7);
|
|
19061
19061
|
if (pathArray.length === 0) {
|
|
19062
19062
|
return false;
|
|
19063
19063
|
}
|
|
@@ -19176,7 +19176,7 @@ var retryifyAsync = (fn, options) => {
|
|
|
19176
19176
|
throw error;
|
|
19177
19177
|
const delay = Math.round(interval * Math.random());
|
|
19178
19178
|
if (delay > 0) {
|
|
19179
|
-
const delayPromise = new Promise((
|
|
19179
|
+
const delayPromise = new Promise((resolve5) => setTimeout(resolve5, delay));
|
|
19180
19180
|
return delayPromise.then(() => attempt.apply(void 0, args));
|
|
19181
19181
|
} else {
|
|
19182
19182
|
return attempt.apply(void 0, args);
|
|
@@ -20180,9 +20180,9 @@ async function getWsToken() {
|
|
|
20180
20180
|
);
|
|
20181
20181
|
return data.token;
|
|
20182
20182
|
}
|
|
20183
|
-
async function api(
|
|
20183
|
+
async function api(path7, options = {}) {
|
|
20184
20184
|
const { accessToken } = getTokens();
|
|
20185
|
-
const url = `${getApiUrl()}${
|
|
20185
|
+
const url = `${getApiUrl()}${path7}`;
|
|
20186
20186
|
const headers = {
|
|
20187
20187
|
"Content-Type": "application/json",
|
|
20188
20188
|
...options.headers || {}
|
|
@@ -20211,6 +20211,60 @@ async function api(path6, options = {}) {
|
|
|
20211
20211
|
}
|
|
20212
20212
|
return res.json();
|
|
20213
20213
|
}
|
|
20214
|
+
async function* streamApi(path7, options = {}) {
|
|
20215
|
+
const { accessToken } = getTokens();
|
|
20216
|
+
const url = `${getApiUrl()}${path7}`;
|
|
20217
|
+
const headers = {
|
|
20218
|
+
"Content-Type": "application/json",
|
|
20219
|
+
Accept: "text/event-stream",
|
|
20220
|
+
...options.headers || {}
|
|
20221
|
+
};
|
|
20222
|
+
if (accessToken) headers["Authorization"] = `Bearer ${accessToken}`;
|
|
20223
|
+
const res = await fetch(url, { ...options, headers });
|
|
20224
|
+
if (!res.ok || !res.body) {
|
|
20225
|
+
let message = `HTTP ${res.status}`;
|
|
20226
|
+
try {
|
|
20227
|
+
const body = await res.json();
|
|
20228
|
+
if (body.error) message = body.error;
|
|
20229
|
+
} catch {
|
|
20230
|
+
}
|
|
20231
|
+
throw new ApiError(res.status, message);
|
|
20232
|
+
}
|
|
20233
|
+
const reader = res.body.getReader();
|
|
20234
|
+
const decoder = new TextDecoder();
|
|
20235
|
+
let buffer = "";
|
|
20236
|
+
try {
|
|
20237
|
+
while (true) {
|
|
20238
|
+
const { value, done } = await reader.read();
|
|
20239
|
+
if (done) break;
|
|
20240
|
+
buffer += decoder.decode(value, { stream: true });
|
|
20241
|
+
let boundary = buffer.indexOf("\n\n");
|
|
20242
|
+
while (boundary !== -1) {
|
|
20243
|
+
const frame = buffer.slice(0, boundary);
|
|
20244
|
+
buffer = buffer.slice(boundary + 2);
|
|
20245
|
+
const dataLines = [];
|
|
20246
|
+
for (const line of frame.split("\n")) {
|
|
20247
|
+
if (line.startsWith("data:")) {
|
|
20248
|
+
dataLines.push(line.slice(5).trimStart());
|
|
20249
|
+
}
|
|
20250
|
+
}
|
|
20251
|
+
if (dataLines.length > 0) {
|
|
20252
|
+
const json = dataLines.join("\n");
|
|
20253
|
+
try {
|
|
20254
|
+
yield JSON.parse(json);
|
|
20255
|
+
} catch {
|
|
20256
|
+
}
|
|
20257
|
+
}
|
|
20258
|
+
boundary = buffer.indexOf("\n\n");
|
|
20259
|
+
}
|
|
20260
|
+
}
|
|
20261
|
+
} finally {
|
|
20262
|
+
try {
|
|
20263
|
+
reader.releaseLock();
|
|
20264
|
+
} catch {
|
|
20265
|
+
}
|
|
20266
|
+
}
|
|
20267
|
+
}
|
|
20214
20268
|
|
|
20215
20269
|
// src/commands/login.ts
|
|
20216
20270
|
function authPage(success, error) {
|
|
@@ -20242,7 +20296,7 @@ function authPage(success, error) {
|
|
|
20242
20296
|
<body>
|
|
20243
20297
|
<div class="card">
|
|
20244
20298
|
<div class="logo">
|
|
20245
|
-
<svg width="24" height="24" viewBox="0 0 52 36" fill="none"><polygon points="44,18 8,6 13,18 8,30" stroke="#C8E0FF" stroke-width="2.5" fill="none"/></svg>
|
|
20299
|
+
<svg width="24" height="24" viewBox="0 0 52 36" fill="none" style="transform:rotate(-45deg)"><polygon points="44,18 8,6 13,18 8,30" stroke="#C8E0FF" stroke-width="2.5" fill="none"/></svg>
|
|
20246
20300
|
Launchmatic
|
|
20247
20301
|
</div>
|
|
20248
20302
|
<div class="icon">${icon}</div>
|
|
@@ -20297,13 +20351,15 @@ function registerLogin(program3) {
|
|
|
20297
20351
|
});
|
|
20298
20352
|
}
|
|
20299
20353
|
function waitForOAuth(apiUrl) {
|
|
20300
|
-
return new Promise((
|
|
20354
|
+
return new Promise((resolve5, reject) => {
|
|
20301
20355
|
let settled = false;
|
|
20302
20356
|
function settle() {
|
|
20303
20357
|
if (settled) return false;
|
|
20304
20358
|
settled = true;
|
|
20305
20359
|
rl.close();
|
|
20306
|
-
process.stdin.unref
|
|
20360
|
+
if (typeof process.stdin.unref === "function") {
|
|
20361
|
+
process.stdin.unref();
|
|
20362
|
+
}
|
|
20307
20363
|
server.close();
|
|
20308
20364
|
server.unref();
|
|
20309
20365
|
return true;
|
|
@@ -20317,7 +20373,7 @@ function waitForOAuth(apiUrl) {
|
|
|
20317
20373
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
20318
20374
|
res.end(authPage(true), () => {
|
|
20319
20375
|
if (settle()) {
|
|
20320
|
-
|
|
20376
|
+
resolve5({ accessToken, refreshToken });
|
|
20321
20377
|
}
|
|
20322
20378
|
});
|
|
20323
20379
|
} else {
|
|
@@ -20341,7 +20397,7 @@ function waitForOAuth(apiUrl) {
|
|
|
20341
20397
|
const rt = pastedUrl.searchParams.get("refresh_token");
|
|
20342
20398
|
if (at && rt) {
|
|
20343
20399
|
if (settle()) {
|
|
20344
|
-
|
|
20400
|
+
resolve5({ accessToken: at, refreshToken: rt });
|
|
20345
20401
|
}
|
|
20346
20402
|
}
|
|
20347
20403
|
} catch {
|
|
@@ -21261,13 +21317,13 @@ function configPath() {
|
|
|
21261
21317
|
return resolve(process.cwd(), CONFIG_FILE);
|
|
21262
21318
|
}
|
|
21263
21319
|
function readContext() {
|
|
21264
|
-
const
|
|
21265
|
-
if (!existsSync(
|
|
21320
|
+
const path7 = configPath();
|
|
21321
|
+
if (!existsSync(path7)) {
|
|
21266
21322
|
throw new Error(
|
|
21267
21323
|
`No ${CONFIG_FILE} found in current directory. Run "lm init" first.`
|
|
21268
21324
|
);
|
|
21269
21325
|
}
|
|
21270
|
-
const raw = readFileSync(
|
|
21326
|
+
const raw = readFileSync(path7, "utf-8");
|
|
21271
21327
|
try {
|
|
21272
21328
|
return JSON.parse(raw);
|
|
21273
21329
|
} catch {
|
|
@@ -21283,9 +21339,9 @@ function contextExists() {
|
|
|
21283
21339
|
return existsSync(configPath());
|
|
21284
21340
|
}
|
|
21285
21341
|
function removeContext() {
|
|
21286
|
-
const
|
|
21287
|
-
if (existsSync(
|
|
21288
|
-
unlinkSync(
|
|
21342
|
+
const path7 = configPath();
|
|
21343
|
+
if (existsSync(path7)) {
|
|
21344
|
+
unlinkSync(path7);
|
|
21289
21345
|
}
|
|
21290
21346
|
}
|
|
21291
21347
|
|
|
@@ -21328,6 +21384,20 @@ function detectLocal(cwd) {
|
|
|
21328
21384
|
if (existsSync2(join(cwd, "package.json"))) {
|
|
21329
21385
|
const pkg = readPkg(cwd);
|
|
21330
21386
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
21387
|
+
if (deps?.["stirrup-ai"]) {
|
|
21388
|
+
return {
|
|
21389
|
+
runtime: "nodejs",
|
|
21390
|
+
framework: "Stirrup",
|
|
21391
|
+
buildCmd: "npm install",
|
|
21392
|
+
startCmd: "npx stirrup serve",
|
|
21393
|
+
port: 3711,
|
|
21394
|
+
confidence: "high",
|
|
21395
|
+
recommendedStorageGi: 2,
|
|
21396
|
+
recommendedStorageMountPath: "/data",
|
|
21397
|
+
recommendedMemory: "1Gi",
|
|
21398
|
+
requiredEnvVars: ["ANTHROPIC_API_KEY"]
|
|
21399
|
+
};
|
|
21400
|
+
}
|
|
21331
21401
|
const hasIndex = existsSync2(join(cwd, "index.html")) || existsSync2(join(cwd, "public", "index.html"));
|
|
21332
21402
|
const hasServerDep = Object.keys(deps || {}).some(
|
|
21333
21403
|
(d) => /^(express|fastify|next|nuxt|remix|gatsby|astro|vite|svelte|@angular|react-scripts|koa|hapi|nestjs)/.test(
|
|
@@ -21733,10 +21803,10 @@ function prompt(question) {
|
|
|
21733
21803
|
input: process.stdin,
|
|
21734
21804
|
output: process.stdout
|
|
21735
21805
|
});
|
|
21736
|
-
return new Promise((
|
|
21806
|
+
return new Promise((resolve5) => {
|
|
21737
21807
|
rl.question(question, (answer) => {
|
|
21738
21808
|
rl.close();
|
|
21739
|
-
|
|
21809
|
+
resolve5(answer.trim());
|
|
21740
21810
|
});
|
|
21741
21811
|
});
|
|
21742
21812
|
}
|
|
@@ -21754,7 +21824,13 @@ var wrapper_default = import_websocket.default;
|
|
|
21754
21824
|
|
|
21755
21825
|
// src/commands/deploy.ts
|
|
21756
21826
|
function registerDeploy(program3) {
|
|
21757
|
-
program3.command("deploy").description("Build and deploy the current service").
|
|
21827
|
+
program3.command("deploy").description("Build and deploy the current service").option(
|
|
21828
|
+
"--dockerfile <path>",
|
|
21829
|
+
"Path to Dockerfile relative to repo root (e.g. apps/web/Dockerfile). Use when build context must be the repo root but the Dockerfile lives in a subdirectory (monorepos). Pass an empty string to clear."
|
|
21830
|
+
).option(
|
|
21831
|
+
"--root-dir <path>",
|
|
21832
|
+
"Root directory for the build (default: /)"
|
|
21833
|
+
).option("--build-cmd <cmd>", "Override the build command").option("--start-cmd <cmd>", "Override the start command").action(async (opts) => {
|
|
21758
21834
|
if (!isLoggedIn()) {
|
|
21759
21835
|
console.error(source_default.red('Not logged in. Run "lm login" first.'));
|
|
21760
21836
|
process.exitCode = 1;
|
|
@@ -21793,13 +21869,60 @@ function registerDeploy(program3) {
|
|
|
21793
21869
|
}
|
|
21794
21870
|
}
|
|
21795
21871
|
if (!gitInfo.hasRemote) {
|
|
21796
|
-
|
|
21797
|
-
|
|
21798
|
-
|
|
21799
|
-
|
|
21800
|
-
|
|
21801
|
-
|
|
21802
|
-
|
|
21872
|
+
const repoSpinner = ora("No git remote \u2014 creating GitHub repository...").start();
|
|
21873
|
+
try {
|
|
21874
|
+
try {
|
|
21875
|
+
execFileSync4("git", ["rev-parse", "--git-dir"], { cwd, stdio: "pipe" });
|
|
21876
|
+
} catch {
|
|
21877
|
+
execFileSync4("git", ["init"], { cwd, stdio: "pipe" });
|
|
21878
|
+
}
|
|
21879
|
+
try {
|
|
21880
|
+
const status = execFileSync4("git", ["status", "--porcelain"], {
|
|
21881
|
+
cwd,
|
|
21882
|
+
encoding: "utf-8",
|
|
21883
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
21884
|
+
});
|
|
21885
|
+
if (status.trim().length > 0) {
|
|
21886
|
+
execFileSync4("git", ["add", "-A"], { cwd, stdio: "pipe" });
|
|
21887
|
+
execFileSync4(
|
|
21888
|
+
"git",
|
|
21889
|
+
["commit", "-m", "Initial commit"],
|
|
21890
|
+
{ cwd, stdio: "pipe" }
|
|
21891
|
+
);
|
|
21892
|
+
}
|
|
21893
|
+
} catch {
|
|
21894
|
+
}
|
|
21895
|
+
const { data: repoData } = await api(`/api/services/${ctx.serviceId}/create-repo`, {
|
|
21896
|
+
method: "POST",
|
|
21897
|
+
body: JSON.stringify({})
|
|
21898
|
+
});
|
|
21899
|
+
try {
|
|
21900
|
+
execFileSync4("git", ["remote", "add", "origin", repoData.cloneUrl], { cwd, stdio: "pipe" });
|
|
21901
|
+
} catch {
|
|
21902
|
+
execFileSync4("git", ["remote", "set-url", "origin", repoData.cloneUrl], { cwd, stdio: "pipe" });
|
|
21903
|
+
}
|
|
21904
|
+
const localBranch = (() => {
|
|
21905
|
+
try {
|
|
21906
|
+
return execFileSync4("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd, encoding: "utf-8" }).trim();
|
|
21907
|
+
} catch {
|
|
21908
|
+
return "main";
|
|
21909
|
+
}
|
|
21910
|
+
})();
|
|
21911
|
+
execFileSync4("git", ["push", "-u", "origin", localBranch], { cwd, stdio: ["pipe", "pipe", "pipe"] });
|
|
21912
|
+
repoSpinner.succeed(`Repository created: ${source_default.dim(repoData.repoFullName)} (private)`);
|
|
21913
|
+
gitInfo = getGitInfo(cwd);
|
|
21914
|
+
} catch (err) {
|
|
21915
|
+
repoSpinner.fail(
|
|
21916
|
+
`Could not auto-create repo: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
21917
|
+
);
|
|
21918
|
+
console.error(
|
|
21919
|
+
source_default.red(
|
|
21920
|
+
"Add a GitHub remote manually (git remote add origin ...) or re-run 'lm init'."
|
|
21921
|
+
)
|
|
21922
|
+
);
|
|
21923
|
+
process.exitCode = 1;
|
|
21924
|
+
return;
|
|
21925
|
+
}
|
|
21803
21926
|
}
|
|
21804
21927
|
const commitSha = gitInfo.commitSha || "";
|
|
21805
21928
|
const shortSha = commitSha ? commitSha.slice(0, 7) : "unknown";
|
|
@@ -21824,6 +21947,12 @@ function registerDeploy(program3) {
|
|
|
21824
21947
|
if (detection.startCmd) patch.startCmd = detection.startCmd;
|
|
21825
21948
|
if (detection.port) patch.port = detection.port;
|
|
21826
21949
|
if (detection.framework) patch.framework = detection.framework;
|
|
21950
|
+
if (opts.dockerfile !== void 0) {
|
|
21951
|
+
patch.dockerfilePath = opts.dockerfile === "" ? null : opts.dockerfile;
|
|
21952
|
+
}
|
|
21953
|
+
if (opts.rootDir) patch.rootDir = opts.rootDir;
|
|
21954
|
+
if (opts.buildCmd) patch.buildCmd = opts.buildCmd;
|
|
21955
|
+
if (opts.startCmd) patch.startCmd = opts.startCmd;
|
|
21827
21956
|
if (Object.keys(patch).length > 0) {
|
|
21828
21957
|
try {
|
|
21829
21958
|
await api(`/api/services/${ctx.serviceId}`, {
|
|
@@ -21847,7 +21976,7 @@ function registerDeploy(program3) {
|
|
|
21847
21976
|
const apiUrl = getApiUrl();
|
|
21848
21977
|
const wsUrl = apiUrl.replace(/^http/, "ws");
|
|
21849
21978
|
const wsToken = await getWsToken();
|
|
21850
|
-
await new Promise((
|
|
21979
|
+
await new Promise((resolve5, reject) => {
|
|
21851
21980
|
let statusResolved = false;
|
|
21852
21981
|
const logWs = new wrapper_default(
|
|
21853
21982
|
`${wsUrl}/ws/logs/${deployment.id}?token=${wsToken}`
|
|
@@ -21888,7 +22017,7 @@ function registerDeploy(program3) {
|
|
|
21888
22017
|
spinner.succeed(
|
|
21889
22018
|
source_default.green(`Deployed \u2192 ${source_default.bold(`https://${url}`)}`)
|
|
21890
22019
|
);
|
|
21891
|
-
|
|
22020
|
+
resolve5();
|
|
21892
22021
|
} else if (parsed.status === "FAILED" && !statusResolved) {
|
|
21893
22022
|
statusResolved = true;
|
|
21894
22023
|
logWs.close();
|
|
@@ -21913,7 +22042,7 @@ function registerDeploy(program3) {
|
|
|
21913
22042
|
if (!statusResolved) {
|
|
21914
22043
|
statusResolved = true;
|
|
21915
22044
|
logWs.close();
|
|
21916
|
-
pollDeploymentStatus(deployment.id, spinner).then(
|
|
22045
|
+
pollDeploymentStatus(deployment.id, spinner).then(resolve5).catch(reject);
|
|
21917
22046
|
}
|
|
21918
22047
|
});
|
|
21919
22048
|
});
|
|
@@ -22236,10 +22365,10 @@ function prompt2(question) {
|
|
|
22236
22365
|
input: process.stdin,
|
|
22237
22366
|
output: process.stdout
|
|
22238
22367
|
});
|
|
22239
|
-
return new Promise((
|
|
22368
|
+
return new Promise((resolve5) => {
|
|
22240
22369
|
rl.question(question, (answer) => {
|
|
22241
22370
|
rl.close();
|
|
22242
|
-
|
|
22371
|
+
resolve5(answer.trim());
|
|
22243
22372
|
});
|
|
22244
22373
|
});
|
|
22245
22374
|
}
|
|
@@ -22254,10 +22383,10 @@ function prompt3(question) {
|
|
|
22254
22383
|
input: process.stdin,
|
|
22255
22384
|
output: process.stdout
|
|
22256
22385
|
});
|
|
22257
|
-
return new Promise((
|
|
22386
|
+
return new Promise((resolve5) => {
|
|
22258
22387
|
rl.question(question, (answer) => {
|
|
22259
22388
|
rl.close();
|
|
22260
|
-
|
|
22389
|
+
resolve5(answer.trim());
|
|
22261
22390
|
});
|
|
22262
22391
|
});
|
|
22263
22392
|
}
|
|
@@ -22503,10 +22632,10 @@ function toSlug2(name) {
|
|
|
22503
22632
|
}
|
|
22504
22633
|
function askInput(question) {
|
|
22505
22634
|
const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
|
|
22506
|
-
return new Promise((
|
|
22635
|
+
return new Promise((resolve5) => {
|
|
22507
22636
|
rl.question(question, (answer) => {
|
|
22508
22637
|
rl.close();
|
|
22509
|
-
|
|
22638
|
+
resolve5(answer.trim());
|
|
22510
22639
|
});
|
|
22511
22640
|
});
|
|
22512
22641
|
}
|
|
@@ -22794,7 +22923,7 @@ Detected: ${runtimeLabel} on port ${detection.port}`));
|
|
|
22794
22923
|
const apiUrl = getApiUrl();
|
|
22795
22924
|
const wsUrl = apiUrl.replace(/^http/, "ws");
|
|
22796
22925
|
const wsToken = await getWsToken();
|
|
22797
|
-
await new Promise((
|
|
22926
|
+
await new Promise((resolve5, reject) => {
|
|
22798
22927
|
let done = false;
|
|
22799
22928
|
const logWs = new wrapper_default(
|
|
22800
22929
|
`${wsUrl}/ws/logs/${deployment.id}?token=${wsToken}`
|
|
@@ -22831,7 +22960,7 @@ Detected: ${runtimeLabel} on port ${detection.port}`));
|
|
|
22831
22960
|
deploySpinner.succeed(
|
|
22832
22961
|
source_default.green(`Live \u2192 ${source_default.bold(`https://${url}`)}`)
|
|
22833
22962
|
);
|
|
22834
|
-
|
|
22963
|
+
resolve5();
|
|
22835
22964
|
} else if (parsed.status === "FAILED" && !done) {
|
|
22836
22965
|
done = true;
|
|
22837
22966
|
logWs.close();
|
|
@@ -22854,7 +22983,7 @@ Detected: ${runtimeLabel} on port ${detection.port}`));
|
|
|
22854
22983
|
if (!done) {
|
|
22855
22984
|
done = true;
|
|
22856
22985
|
logWs.close();
|
|
22857
|
-
pollStatus(deployment.id, deploySpinner).then(
|
|
22986
|
+
pollStatus(deployment.id, deploySpinner).then(resolve5).catch(reject);
|
|
22858
22987
|
}
|
|
22859
22988
|
});
|
|
22860
22989
|
});
|
|
@@ -22916,12 +23045,12 @@ async function ensureChromium() {
|
|
|
22916
23045
|
process.exit(1);
|
|
22917
23046
|
}
|
|
22918
23047
|
}
|
|
22919
|
-
const
|
|
22920
|
-
if (!
|
|
23048
|
+
const path7 = findChromium();
|
|
23049
|
+
if (!path7) {
|
|
22921
23050
|
console.error(source_default.red("Chromium installed but not found on disk."));
|
|
22922
23051
|
process.exit(1);
|
|
22923
23052
|
}
|
|
22924
|
-
return
|
|
23053
|
+
return path7;
|
|
22925
23054
|
}
|
|
22926
23055
|
async function launchBrowser(headless = true) {
|
|
22927
23056
|
const pw = await import("playwright-core");
|
|
@@ -23249,8 +23378,8 @@ function getRepo() {
|
|
|
23249
23378
|
}
|
|
23250
23379
|
return { owner: info.repoOwner, name: info.repoName, branch: info.repoBranch };
|
|
23251
23380
|
}
|
|
23252
|
-
async function ghApi(
|
|
23253
|
-
return api(`/api/github${
|
|
23381
|
+
async function ghApi(path7, options) {
|
|
23382
|
+
return api(`/api/github${path7}`, options);
|
|
23254
23383
|
}
|
|
23255
23384
|
function timeAgo(date) {
|
|
23256
23385
|
const diff = Date.now() - new Date(date).getTime();
|
|
@@ -23340,9 +23469,9 @@ function registerRepo(program3) {
|
|
|
23340
23469
|
}
|
|
23341
23470
|
const spinner = ora("Fetching issues...").start();
|
|
23342
23471
|
try {
|
|
23343
|
-
let
|
|
23344
|
-
if (opts.labels)
|
|
23345
|
-
const { data } = await ghApi(
|
|
23472
|
+
let path7 = `/repos/${owner}/${name}/issues?state=${opts.state}&limit=${opts.limit}`;
|
|
23473
|
+
if (opts.labels) path7 += `&labels=${encodeURIComponent(opts.labels)}`;
|
|
23474
|
+
const { data } = await ghApi(path7);
|
|
23346
23475
|
spinner.stop();
|
|
23347
23476
|
const issues = (data || []).filter((i) => !i.pull_request);
|
|
23348
23477
|
if (issues.length === 0) {
|
|
@@ -23510,11 +23639,11 @@ function registerRepo(program3) {
|
|
|
23510
23639
|
process.exitCode = 1;
|
|
23511
23640
|
}
|
|
23512
23641
|
});
|
|
23513
|
-
repo.command("browse").alias("web").description("Open repository in browser").argument("[path]", "File or path to open").option("-b, --branch <branch>", "Branch name").action(async (
|
|
23642
|
+
repo.command("browse").alias("web").description("Open repository in browser").argument("[path]", "File or path to open").option("-b, --branch <branch>", "Branch name").action(async (path7, opts) => {
|
|
23514
23643
|
const { owner, name, branch: currentBranch } = getRepo();
|
|
23515
23644
|
const branch = opts.branch || currentBranch;
|
|
23516
23645
|
let url = `https://github.com/${owner}/${name}`;
|
|
23517
|
-
if (
|
|
23646
|
+
if (path7) url += `/blob/${branch}/${path7}`;
|
|
23518
23647
|
else if (branch !== "main" && branch !== "master") url += `/tree/${branch}`;
|
|
23519
23648
|
openUrl(url);
|
|
23520
23649
|
console.log(source_default.dim(`Opened ${url}`));
|
|
@@ -23578,10 +23707,10 @@ async function getTeamId() {
|
|
|
23578
23707
|
}
|
|
23579
23708
|
function prompt4(question) {
|
|
23580
23709
|
const rl = readline5.createInterface({ input: process.stdin, output: process.stdout });
|
|
23581
|
-
return new Promise((
|
|
23710
|
+
return new Promise((resolve5) => {
|
|
23582
23711
|
rl.question(question, (answer) => {
|
|
23583
23712
|
rl.close();
|
|
23584
|
-
|
|
23713
|
+
resolve5(answer.trim());
|
|
23585
23714
|
});
|
|
23586
23715
|
});
|
|
23587
23716
|
}
|
|
@@ -23677,13 +23806,13 @@ function registerDb(program3) {
|
|
|
23677
23806
|
const url = data.connectionUrl;
|
|
23678
23807
|
if (opts.copy) {
|
|
23679
23808
|
try {
|
|
23680
|
-
const { execFileSync:
|
|
23809
|
+
const { execFileSync: execFileSync10 } = await import("child_process");
|
|
23681
23810
|
if (process.platform === "win32") {
|
|
23682
|
-
|
|
23811
|
+
execFileSync10("cmd", ["/c", `echo|set /p="${url}"| clip`], { stdio: "pipe" });
|
|
23683
23812
|
} else if (process.platform === "darwin") {
|
|
23684
|
-
|
|
23813
|
+
execFileSync10("pbcopy", [], { input: url, stdio: ["pipe", "pipe", "pipe"] });
|
|
23685
23814
|
} else {
|
|
23686
|
-
|
|
23815
|
+
execFileSync10("xclip", ["-selection", "clipboard"], { input: url, stdio: ["pipe", "pipe", "pipe"] });
|
|
23687
23816
|
}
|
|
23688
23817
|
console.log(source_default.green("\u2713 Connection URL copied to clipboard"));
|
|
23689
23818
|
} catch {
|
|
@@ -23846,7 +23975,7 @@ function registerDb(program3) {
|
|
|
23846
23975
|
process.exitCode = 1;
|
|
23847
23976
|
}
|
|
23848
23977
|
});
|
|
23849
|
-
db.command("create <name>").description("Create a new database").option("-e, --engine <engine>", "Engine: postgresql, redis, mongodb", "postgresql").option("-v, --version <version>", "Engine version").option("-s, --storage <mb>", "Storage size in MB", "1024").option("--service <serviceId>", "Link to a service").option("--project <projectId>", "Project to attach to").action(async (name, opts) => {
|
|
23978
|
+
db.command("create <name>").description("Create a new database").option("-e, --engine <engine>", "Engine: postgresql, postgis, redis, mongodb", "postgresql").option("-v, --version <version>", "Engine version").option("-s, --storage <mb>", "Storage size in MB", "1024").option("--service <serviceId>", "Link to a service").option("--project <projectId>", "Project to attach to").action(async (name, opts) => {
|
|
23850
23979
|
requireLogin2();
|
|
23851
23980
|
const spinner = ora(`Creating ${opts.engine} database "${name}"...`).start();
|
|
23852
23981
|
try {
|
|
@@ -23921,8 +24050,8 @@ function registerDb(program3) {
|
|
|
23921
24050
|
body: JSON.stringify({ command: ["pg_dump", "-U", "postgres", "--no-owner", "--no-acl"] })
|
|
23922
24051
|
});
|
|
23923
24052
|
if (opts.output) {
|
|
23924
|
-
const { writeFileSync:
|
|
23925
|
-
|
|
24053
|
+
const { writeFileSync: writeFileSync4 } = await import("fs");
|
|
24054
|
+
writeFileSync4(opts.output, data.output);
|
|
23926
24055
|
spinner?.succeed(`Dump saved \u2192 ${source_default.cyan(opts.output)}`);
|
|
23927
24056
|
} else {
|
|
23928
24057
|
process.stdout.write(data.output);
|
|
@@ -23950,8 +24079,8 @@ function registerDb(program3) {
|
|
|
23950
24079
|
requireLogin2();
|
|
23951
24080
|
const spinner = ora(`Running ${file}...`).start();
|
|
23952
24081
|
try {
|
|
23953
|
-
const { readFileSync:
|
|
23954
|
-
const sql =
|
|
24082
|
+
const { readFileSync: readFileSync8 } = await import("fs");
|
|
24083
|
+
const sql = readFileSync8(file, "utf-8");
|
|
23955
24084
|
const { data } = await api(`/api/databases/${dbId}/query`, {
|
|
23956
24085
|
method: "POST",
|
|
23957
24086
|
body: JSON.stringify({ sql })
|
|
@@ -25179,9 +25308,1122 @@ function registerDeployments(program3) {
|
|
|
25179
25308
|
});
|
|
25180
25309
|
}
|
|
25181
25310
|
|
|
25311
|
+
// src/commands/agent.ts
|
|
25312
|
+
import readline6 from "readline";
|
|
25313
|
+
async function callAgent(message, threadId, systemHint) {
|
|
25314
|
+
return api("/api/v1/agent", {
|
|
25315
|
+
method: "POST",
|
|
25316
|
+
body: JSON.stringify({
|
|
25317
|
+
message,
|
|
25318
|
+
thread_id: threadId,
|
|
25319
|
+
system_hint: systemHint
|
|
25320
|
+
})
|
|
25321
|
+
});
|
|
25322
|
+
}
|
|
25323
|
+
async function streamAgentToTerminal(message, threadId, systemHint, showTools) {
|
|
25324
|
+
const events = streamApi("/api/v1/agent/stream", {
|
|
25325
|
+
method: "POST",
|
|
25326
|
+
body: JSON.stringify({
|
|
25327
|
+
message,
|
|
25328
|
+
thread_id: threadId,
|
|
25329
|
+
system_hint: systemHint
|
|
25330
|
+
})
|
|
25331
|
+
});
|
|
25332
|
+
const summary = {
|
|
25333
|
+
threadId: null,
|
|
25334
|
+
text: "",
|
|
25335
|
+
toolCalls: 0,
|
|
25336
|
+
iterations: 0,
|
|
25337
|
+
failed: false
|
|
25338
|
+
};
|
|
25339
|
+
let textStarted = false;
|
|
25340
|
+
for await (const event of events) {
|
|
25341
|
+
switch (event.type) {
|
|
25342
|
+
case "thread":
|
|
25343
|
+
summary.threadId = event.thread_id;
|
|
25344
|
+
break;
|
|
25345
|
+
case "iteration_start":
|
|
25346
|
+
summary.iterations = event.n;
|
|
25347
|
+
break;
|
|
25348
|
+
case "text_delta":
|
|
25349
|
+
if (!textStarted) {
|
|
25350
|
+
process.stdout.write(source_default.cyan("agent: "));
|
|
25351
|
+
textStarted = true;
|
|
25352
|
+
}
|
|
25353
|
+
process.stdout.write(event.delta);
|
|
25354
|
+
summary.text += event.delta;
|
|
25355
|
+
break;
|
|
25356
|
+
case "tool_call":
|
|
25357
|
+
if (showTools) {
|
|
25358
|
+
if (textStarted) process.stdout.write("\n");
|
|
25359
|
+
process.stdout.write(source_default.dim(` \u2192 ${event.name}`));
|
|
25360
|
+
summary.toolCalls++;
|
|
25361
|
+
}
|
|
25362
|
+
break;
|
|
25363
|
+
case "tool_result":
|
|
25364
|
+
if (showTools) {
|
|
25365
|
+
if (event.ok) process.stdout.write(source_default.dim(` \u2713 ${event.summary}
|
|
25366
|
+
`));
|
|
25367
|
+
else process.stdout.write(source_default.red(` \u2717 ${event.summary}
|
|
25368
|
+
`));
|
|
25369
|
+
textStarted = false;
|
|
25370
|
+
}
|
|
25371
|
+
break;
|
|
25372
|
+
case "error":
|
|
25373
|
+
if (textStarted) process.stdout.write("\n");
|
|
25374
|
+
process.stdout.write(source_default.red(`error: ${event.message}
|
|
25375
|
+
`));
|
|
25376
|
+
summary.failed = true;
|
|
25377
|
+
break;
|
|
25378
|
+
case "done":
|
|
25379
|
+
if (textStarted) process.stdout.write("\n");
|
|
25380
|
+
break;
|
|
25381
|
+
}
|
|
25382
|
+
}
|
|
25383
|
+
return summary;
|
|
25384
|
+
}
|
|
25385
|
+
function buildSystemHint(opts) {
|
|
25386
|
+
const parts = [];
|
|
25387
|
+
if (contextExists()) {
|
|
25388
|
+
try {
|
|
25389
|
+
const ctx = readContext();
|
|
25390
|
+
parts.push(`Default project: ${ctx.projectId}. Default service: ${ctx.serviceId}.`);
|
|
25391
|
+
} catch {
|
|
25392
|
+
}
|
|
25393
|
+
}
|
|
25394
|
+
if (opts.service) parts.push(`User explicitly scoped this session to service: ${opts.service}.`);
|
|
25395
|
+
if (opts.environment) parts.push(`User explicitly scoped this session to environment: ${opts.environment}.`);
|
|
25396
|
+
return parts.length > 0 ? parts.join(" ") : null;
|
|
25397
|
+
}
|
|
25398
|
+
function printToolTrace(calls) {
|
|
25399
|
+
for (const call of calls) {
|
|
25400
|
+
const icon = call.error ? source_default.red("\u2717") : source_default.dim("\u2192");
|
|
25401
|
+
const summary = call.error ? source_default.red(call.error) : source_default.dim("ok");
|
|
25402
|
+
console.log(` ${icon} ${source_default.cyan(call.name)} ${summary}`);
|
|
25403
|
+
}
|
|
25404
|
+
}
|
|
25405
|
+
function prompt5(question) {
|
|
25406
|
+
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
25407
|
+
return new Promise((resolve5) => {
|
|
25408
|
+
rl.question(question, (answer) => {
|
|
25409
|
+
rl.close();
|
|
25410
|
+
resolve5(answer);
|
|
25411
|
+
});
|
|
25412
|
+
});
|
|
25413
|
+
}
|
|
25414
|
+
function registerAgent(program3) {
|
|
25415
|
+
program3.command("agent").description("Talk to the Launchmatic agent \u2014 multi-step reasoning over your services").option("-p, --prompt <text>", "Run a single prompt and exit (non-interactive)").option("--json", "Emit machine-readable JSON instead of human-friendly text (disables streaming)").option("--no-stream", "Buffer the full response before printing (default: stream)").option("--thread-id <id>", "Continue an existing thread").option("--service <serviceId>", "Scope the session to a specific service").option("--environment <env>", "Scope the session to a specific environment (e.g. preview, production)").option("--show-tools", "Show each tool call the agent makes (default off in human mode)").action(async (opts) => {
|
|
25416
|
+
if (!isLoggedIn()) {
|
|
25417
|
+
console.error(source_default.red('Not logged in. Run "lm login" first.'));
|
|
25418
|
+
process.exitCode = 1;
|
|
25419
|
+
return;
|
|
25420
|
+
}
|
|
25421
|
+
const systemHint = buildSystemHint(opts);
|
|
25422
|
+
let threadId = opts.threadId ?? null;
|
|
25423
|
+
const useStream = opts.stream !== false && !opts.json;
|
|
25424
|
+
if (opts.prompt) {
|
|
25425
|
+
if (opts.json) {
|
|
25426
|
+
try {
|
|
25427
|
+
const res = await callAgent(opts.prompt, threadId, systemHint);
|
|
25428
|
+
console.log(JSON.stringify(res, null, 2));
|
|
25429
|
+
} catch (err) {
|
|
25430
|
+
console.error(source_default.red(`Agent error: ${err instanceof Error ? err.message : String(err)}`));
|
|
25431
|
+
process.exitCode = 1;
|
|
25432
|
+
}
|
|
25433
|
+
return;
|
|
25434
|
+
}
|
|
25435
|
+
if (useStream) {
|
|
25436
|
+
try {
|
|
25437
|
+
const summary = await streamAgentToTerminal(opts.prompt, threadId, systemHint, opts.showTools ?? false);
|
|
25438
|
+
if (summary.failed) {
|
|
25439
|
+
process.exitCode = 1;
|
|
25440
|
+
return;
|
|
25441
|
+
}
|
|
25442
|
+
if (summary.threadId) {
|
|
25443
|
+
console.log();
|
|
25444
|
+
console.log(source_default.dim(`Thread: ${summary.threadId} (resume with --thread-id ${summary.threadId})`));
|
|
25445
|
+
}
|
|
25446
|
+
} catch (err) {
|
|
25447
|
+
console.error(source_default.red(`Agent error: ${err instanceof Error ? err.message : String(err)}`));
|
|
25448
|
+
process.exitCode = 1;
|
|
25449
|
+
}
|
|
25450
|
+
return;
|
|
25451
|
+
}
|
|
25452
|
+
const spinner = ora("Thinking...").start();
|
|
25453
|
+
try {
|
|
25454
|
+
const res = await callAgent(opts.prompt, threadId, systemHint);
|
|
25455
|
+
spinner.stop();
|
|
25456
|
+
if (opts.showTools && res.tool_calls.length > 0) {
|
|
25457
|
+
printToolTrace(res.tool_calls);
|
|
25458
|
+
console.log();
|
|
25459
|
+
}
|
|
25460
|
+
console.log(res.text);
|
|
25461
|
+
console.log();
|
|
25462
|
+
console.log(source_default.dim(`Thread: ${res.thread_id} (resume with --thread-id ${res.thread_id})`));
|
|
25463
|
+
} catch (err) {
|
|
25464
|
+
spinner.fail(source_default.red(`Agent error: ${err instanceof Error ? err.message : String(err)}`));
|
|
25465
|
+
process.exitCode = 1;
|
|
25466
|
+
}
|
|
25467
|
+
return;
|
|
25468
|
+
}
|
|
25469
|
+
console.log(source_default.bold.cyan("\n Launchmatic Agent") + source_default.dim(" \u2014 type your request, /help for commands, /quit to exit"));
|
|
25470
|
+
if (threadId) console.log(source_default.dim(` Resuming thread ${threadId}
|
|
25471
|
+
`));
|
|
25472
|
+
else console.log();
|
|
25473
|
+
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
25474
|
+
const handleLine = async (line) => {
|
|
25475
|
+
const text = line.trim();
|
|
25476
|
+
if (!text) return false;
|
|
25477
|
+
if (text === "/quit" || text === "/exit") return true;
|
|
25478
|
+
if (text === "/help") {
|
|
25479
|
+
console.log(source_default.dim(" /thread show current thread id"));
|
|
25480
|
+
console.log(source_default.dim(" /new start a new thread"));
|
|
25481
|
+
console.log(source_default.dim(" /tools toggle tool-call display"));
|
|
25482
|
+
console.log(source_default.dim(" /quit exit"));
|
|
25483
|
+
return false;
|
|
25484
|
+
}
|
|
25485
|
+
if (text === "/thread") {
|
|
25486
|
+
console.log(source_default.dim(` ${threadId ?? "(no thread yet)"}`));
|
|
25487
|
+
return false;
|
|
25488
|
+
}
|
|
25489
|
+
if (text === "/new") {
|
|
25490
|
+
threadId = null;
|
|
25491
|
+
console.log(source_default.dim(" Started fresh thread."));
|
|
25492
|
+
return false;
|
|
25493
|
+
}
|
|
25494
|
+
if (text === "/tools") {
|
|
25495
|
+
opts.showTools = !opts.showTools;
|
|
25496
|
+
console.log(source_default.dim(` show-tools: ${opts.showTools ? "on" : "off"}`));
|
|
25497
|
+
return false;
|
|
25498
|
+
}
|
|
25499
|
+
if (useStream) {
|
|
25500
|
+
try {
|
|
25501
|
+
const summary = await streamAgentToTerminal(text, threadId, systemHint, opts.showTools ?? false);
|
|
25502
|
+
if (summary.threadId) threadId = summary.threadId;
|
|
25503
|
+
console.log();
|
|
25504
|
+
} catch (err) {
|
|
25505
|
+
console.error(source_default.red(`Agent error: ${err instanceof Error ? err.message : String(err)}`));
|
|
25506
|
+
}
|
|
25507
|
+
return false;
|
|
25508
|
+
}
|
|
25509
|
+
const spinner = ora("Thinking...").start();
|
|
25510
|
+
try {
|
|
25511
|
+
const res = await callAgent(text, threadId, systemHint);
|
|
25512
|
+
threadId = res.thread_id;
|
|
25513
|
+
spinner.stop();
|
|
25514
|
+
if (opts.showTools && res.tool_calls.length > 0) {
|
|
25515
|
+
printToolTrace(res.tool_calls);
|
|
25516
|
+
console.log();
|
|
25517
|
+
}
|
|
25518
|
+
console.log(source_default.cyan("agent:"), res.text);
|
|
25519
|
+
console.log();
|
|
25520
|
+
} catch (err) {
|
|
25521
|
+
spinner.fail(source_default.red(`Agent error: ${err instanceof Error ? err.message : String(err)}`));
|
|
25522
|
+
}
|
|
25523
|
+
return false;
|
|
25524
|
+
};
|
|
25525
|
+
let done = false;
|
|
25526
|
+
while (!done) {
|
|
25527
|
+
const line = await new Promise((resolve5) => {
|
|
25528
|
+
rl.question(source_default.cyan("> "), (answer) => resolve5(answer));
|
|
25529
|
+
rl.once("close", () => resolve5(null));
|
|
25530
|
+
});
|
|
25531
|
+
if (line === null) {
|
|
25532
|
+
done = true;
|
|
25533
|
+
break;
|
|
25534
|
+
}
|
|
25535
|
+
done = await handleLine(line);
|
|
25536
|
+
}
|
|
25537
|
+
rl.close();
|
|
25538
|
+
console.log(source_default.dim("\n Bye."));
|
|
25539
|
+
void prompt5;
|
|
25540
|
+
});
|
|
25541
|
+
}
|
|
25542
|
+
|
|
25543
|
+
// src/commands/workflows.ts
|
|
25544
|
+
async function loadStirrupServices() {
|
|
25545
|
+
const teams = await api("/api/teams");
|
|
25546
|
+
const teamIds = teams.success ? teams.data.map((t) => t.id) : [];
|
|
25547
|
+
const allServices = [];
|
|
25548
|
+
for (const teamId of teamIds) {
|
|
25549
|
+
const projects = await api(
|
|
25550
|
+
`/api/projects?teamId=${teamId}`
|
|
25551
|
+
);
|
|
25552
|
+
if (!projects.success) continue;
|
|
25553
|
+
for (const project of projects.data) {
|
|
25554
|
+
const services = await api(
|
|
25555
|
+
`/api/services?projectId=${project.id}`
|
|
25556
|
+
);
|
|
25557
|
+
if (!services.success) continue;
|
|
25558
|
+
for (const svc of services.data) {
|
|
25559
|
+
if (svc.framework === "Stirrup") {
|
|
25560
|
+
allServices.push({ ...svc, projectName: project.name });
|
|
25561
|
+
}
|
|
25562
|
+
}
|
|
25563
|
+
}
|
|
25564
|
+
}
|
|
25565
|
+
return allServices;
|
|
25566
|
+
}
|
|
25567
|
+
async function listAllWorkflows() {
|
|
25568
|
+
const spin = ora("Discovering Stirrup services...").start();
|
|
25569
|
+
let services;
|
|
25570
|
+
try {
|
|
25571
|
+
services = await loadStirrupServices();
|
|
25572
|
+
} catch (err) {
|
|
25573
|
+
spin.fail(`Could not list services: ${err instanceof Error ? err.message : String(err)}`);
|
|
25574
|
+
process.exit(1);
|
|
25575
|
+
}
|
|
25576
|
+
spin.succeed(`Found ${services.length} Stirrup service${services.length === 1 ? "" : "s"}`);
|
|
25577
|
+
if (services.length === 0) {
|
|
25578
|
+
console.log(source_default.dim("\nNo Stirrup services deployed. Push a stirrup-ai project and try again."));
|
|
25579
|
+
return;
|
|
25580
|
+
}
|
|
25581
|
+
for (const svc of services) {
|
|
25582
|
+
console.log();
|
|
25583
|
+
console.log(source_default.bold(`${svc.projectName} / ${svc.slug}`) + source_default.dim(` (${svc.id})`));
|
|
25584
|
+
try {
|
|
25585
|
+
const res = await api(`/api/services/${svc.id}/workflows`);
|
|
25586
|
+
if (!res.success || !res.data) {
|
|
25587
|
+
console.log(source_default.red(" could not load workflows"));
|
|
25588
|
+
continue;
|
|
25589
|
+
}
|
|
25590
|
+
if (res.data.requiresAuth) {
|
|
25591
|
+
console.log(source_default.yellow(" needs STIRRUP_API_TOKEN env var"));
|
|
25592
|
+
if (res.data.note) console.log(source_default.dim(" " + res.data.note));
|
|
25593
|
+
continue;
|
|
25594
|
+
}
|
|
25595
|
+
if (res.data.workflows.length === 0) {
|
|
25596
|
+
console.log(source_default.dim(" no workflows defined"));
|
|
25597
|
+
continue;
|
|
25598
|
+
}
|
|
25599
|
+
for (const wf of res.data.workflows) {
|
|
25600
|
+
console.log(
|
|
25601
|
+
` ${source_default.cyan(wf.id)} ${source_default.dim(`(${wf.nodeCount} nodes${wf.triggers.length ? `, triggers: ${wf.triggers.join(",")}` : ""})`)}`
|
|
25602
|
+
);
|
|
25603
|
+
if (wf.description) console.log(source_default.dim(` ${wf.description}`));
|
|
25604
|
+
}
|
|
25605
|
+
} catch (err) {
|
|
25606
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
25607
|
+
console.log(source_default.red(` unreachable: ${msg}`));
|
|
25608
|
+
}
|
|
25609
|
+
}
|
|
25610
|
+
console.log();
|
|
25611
|
+
console.log(source_default.dim("Run a workflow:") + " " + source_default.bold("lm workflows run <serviceId>/<workflowId> --input '{...}'"));
|
|
25612
|
+
}
|
|
25613
|
+
async function runWorkflow(target, opts) {
|
|
25614
|
+
const m = target.match(/^([^/]+)\/(.+)$/);
|
|
25615
|
+
if (!m) {
|
|
25616
|
+
console.error(source_default.red("Target must be <serviceId>/<workflowId>"));
|
|
25617
|
+
process.exit(1);
|
|
25618
|
+
}
|
|
25619
|
+
const [, serviceId, workflowId] = m;
|
|
25620
|
+
let input = {};
|
|
25621
|
+
if (opts.input) {
|
|
25622
|
+
try {
|
|
25623
|
+
input = JSON.parse(opts.input);
|
|
25624
|
+
} catch (err) {
|
|
25625
|
+
console.error(source_default.red(`Input must be valid JSON: ${err instanceof Error ? err.message : String(err)}`));
|
|
25626
|
+
process.exit(1);
|
|
25627
|
+
}
|
|
25628
|
+
}
|
|
25629
|
+
const spin = ora(`Triggering ${source_default.cyan(workflowId)}...`).start();
|
|
25630
|
+
try {
|
|
25631
|
+
const res = await api(
|
|
25632
|
+
`/api/services/${serviceId}/workflows/${encodeURIComponent(workflowId)}/run`,
|
|
25633
|
+
{ method: "POST", body: JSON.stringify(input) }
|
|
25634
|
+
);
|
|
25635
|
+
if (!res.success) {
|
|
25636
|
+
spin.fail(`Trigger failed: ${typeof res.error === "string" ? res.error : JSON.stringify(res.error)}`);
|
|
25637
|
+
process.exit(1);
|
|
25638
|
+
}
|
|
25639
|
+
spin.succeed("Triggered");
|
|
25640
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
25641
|
+
} catch (err) {
|
|
25642
|
+
spin.fail(`Trigger failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
25643
|
+
process.exit(1);
|
|
25644
|
+
}
|
|
25645
|
+
}
|
|
25646
|
+
async function showRuns(serviceId) {
|
|
25647
|
+
const spin = ora("Loading recent runs...").start();
|
|
25648
|
+
try {
|
|
25649
|
+
const res = await api(
|
|
25650
|
+
`/api/services/${serviceId}/workflows/runs`
|
|
25651
|
+
);
|
|
25652
|
+
spin.stop();
|
|
25653
|
+
if (!res.success || !res.data) {
|
|
25654
|
+
console.log(source_default.red("Could not load runs."));
|
|
25655
|
+
return;
|
|
25656
|
+
}
|
|
25657
|
+
if (res.data.runs.length === 0) {
|
|
25658
|
+
console.log(source_default.dim("No runs yet."));
|
|
25659
|
+
return;
|
|
25660
|
+
}
|
|
25661
|
+
for (const r of res.data.runs) {
|
|
25662
|
+
const dur = r.durationMs != null ? `${(r.durationMs / 1e3).toFixed(1)}s` : "\u2014";
|
|
25663
|
+
console.log(
|
|
25664
|
+
`${source_default.cyan(r.workflowId)} ${source_default.dim(r.id)} ${source_default.bold(r.status)} ${source_default.dim(dur)} ${source_default.dim(r.startedAt ?? "")}`
|
|
25665
|
+
);
|
|
25666
|
+
}
|
|
25667
|
+
} catch (err) {
|
|
25668
|
+
spin.fail(`Could not load runs: ${err instanceof Error ? err.message : String(err)}`);
|
|
25669
|
+
}
|
|
25670
|
+
}
|
|
25671
|
+
function registerWorkflows(program3) {
|
|
25672
|
+
const cmd = program3.command("workflows").alias("wf").description("List and trigger Stirrup workflows on your deployed services");
|
|
25673
|
+
cmd.command("list").alias("ls").description("List Stirrup workflows across all your services").action(listAllWorkflows);
|
|
25674
|
+
cmd.command("run <target>").description("Trigger a workflow. Target format: <serviceId>/<workflowId>").option("-i, --input <json>", "JSON input payload to send to the workflow").action(runWorkflow);
|
|
25675
|
+
cmd.command("runs <serviceId>").description("Show recent runs for the workflows on a service").action(showRuns);
|
|
25676
|
+
}
|
|
25677
|
+
|
|
25678
|
+
// src/commands/image.ts
|
|
25679
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
25680
|
+
import path6 from "path";
|
|
25681
|
+
function extFromMime(mime) {
|
|
25682
|
+
if (mime.includes("png")) return "png";
|
|
25683
|
+
if (mime.includes("jpeg") || mime.includes("jpg")) return "jpg";
|
|
25684
|
+
if (mime.includes("webp")) return "webp";
|
|
25685
|
+
if (mime.includes("gif")) return "gif";
|
|
25686
|
+
return "bin";
|
|
25687
|
+
}
|
|
25688
|
+
function slugifyForFilename(s) {
|
|
25689
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "image";
|
|
25690
|
+
}
|
|
25691
|
+
async function generateCmd(promptWords, opts) {
|
|
25692
|
+
if (!isLoggedIn()) {
|
|
25693
|
+
console.error(source_default.red('Not logged in. Run "lm login" first.'));
|
|
25694
|
+
process.exitCode = 1;
|
|
25695
|
+
return;
|
|
25696
|
+
}
|
|
25697
|
+
const userPrompt = promptWords.join(" ").trim();
|
|
25698
|
+
if (!userPrompt) {
|
|
25699
|
+
console.error(source_default.red('Prompt is required. Usage: lm image gen "a watercolor mountain"'));
|
|
25700
|
+
process.exitCode = 1;
|
|
25701
|
+
return;
|
|
25702
|
+
}
|
|
25703
|
+
const count = Math.min(Math.max(parseInt(opts.count ?? "1") || 1, 1), 4);
|
|
25704
|
+
const validRatios = ["1:1", "16:9", "9:16", "4:3", "3:4"];
|
|
25705
|
+
const aspectRatio = opts.aspectRatio && validRatios.includes(opts.aspectRatio) ? opts.aspectRatio : "1:1";
|
|
25706
|
+
const outDir = path6.resolve(opts.outDir ?? process.cwd());
|
|
25707
|
+
const spinner = ora(`Generating ${count} image${count === 1 ? "" : "s"} (${aspectRatio})...`).start();
|
|
25708
|
+
try {
|
|
25709
|
+
const res = await api("/api/nanobanana2/generate", {
|
|
25710
|
+
method: "POST",
|
|
25711
|
+
body: JSON.stringify({ prompt: userPrompt, count, aspectRatio })
|
|
25712
|
+
});
|
|
25713
|
+
spinner.text = "Writing files...";
|
|
25714
|
+
await mkdir(outDir, { recursive: true });
|
|
25715
|
+
const baseName = opts.name ? slugifyForFilename(opts.name) : slugifyForFilename(userPrompt);
|
|
25716
|
+
const written = [];
|
|
25717
|
+
for (let i = 0; i < res.data.images.length; i++) {
|
|
25718
|
+
const img = res.data.images[i];
|
|
25719
|
+
const ext = extFromMime(img.mimeType);
|
|
25720
|
+
const suffix = res.data.images.length === 1 ? "" : `-${i + 1}`;
|
|
25721
|
+
const fileName = `${baseName}${suffix}.${ext}`;
|
|
25722
|
+
const filePath = path6.join(outDir, fileName);
|
|
25723
|
+
await writeFile(filePath, Buffer.from(img.data, "base64"));
|
|
25724
|
+
written.push(filePath);
|
|
25725
|
+
}
|
|
25726
|
+
spinner.succeed(
|
|
25727
|
+
source_default.green(
|
|
25728
|
+
`Wrote ${written.length} image${written.length === 1 ? "" : "s"} ${source_default.dim(`(${res.data.usage.totalTokens} tokens)`)}`
|
|
25729
|
+
)
|
|
25730
|
+
);
|
|
25731
|
+
for (const p of written) console.log(" " + source_default.dim(p));
|
|
25732
|
+
if (res.data.text?.trim()) {
|
|
25733
|
+
console.log();
|
|
25734
|
+
console.log(source_default.dim(res.data.text.trim()));
|
|
25735
|
+
}
|
|
25736
|
+
} catch (err) {
|
|
25737
|
+
spinner.fail(source_default.red(`Generate failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
25738
|
+
process.exitCode = 1;
|
|
25739
|
+
}
|
|
25740
|
+
}
|
|
25741
|
+
async function usageCmd() {
|
|
25742
|
+
if (!isLoggedIn()) {
|
|
25743
|
+
console.error(source_default.red('Not logged in. Run "lm login" first.'));
|
|
25744
|
+
process.exitCode = 1;
|
|
25745
|
+
return;
|
|
25746
|
+
}
|
|
25747
|
+
const spin = ora("Fetching usage...").start();
|
|
25748
|
+
try {
|
|
25749
|
+
const res = await api("/api/nanobanana2/usage");
|
|
25750
|
+
spin.stop();
|
|
25751
|
+
const d = res.data;
|
|
25752
|
+
console.log(source_default.bold(`Plan: ${d.planTier}`));
|
|
25753
|
+
console.log(
|
|
25754
|
+
`Tokens: ${source_default.cyan(d.tokens.used.toLocaleString())} / ${d.tokens.limit.toLocaleString()} ` + source_default.dim(`(${d.tokens.remaining.toLocaleString()} remaining)`)
|
|
25755
|
+
);
|
|
25756
|
+
console.log(
|
|
25757
|
+
`Images: ${source_default.cyan(d.images.used.toString())} / ${d.images.limit.toString()} ` + source_default.dim(`(${d.images.remaining} remaining)`)
|
|
25758
|
+
);
|
|
25759
|
+
console.log(source_default.dim(`Resets: ${new Date(d.resetAt).toLocaleString()}`));
|
|
25760
|
+
} catch (err) {
|
|
25761
|
+
spin.fail(source_default.red(`Usage failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
25762
|
+
process.exitCode = 1;
|
|
25763
|
+
}
|
|
25764
|
+
}
|
|
25765
|
+
async function historyCmd(opts) {
|
|
25766
|
+
if (!isLoggedIn()) {
|
|
25767
|
+
console.error(source_default.red('Not logged in. Run "lm login" first.'));
|
|
25768
|
+
process.exitCode = 1;
|
|
25769
|
+
return;
|
|
25770
|
+
}
|
|
25771
|
+
const limit = Math.min(Math.max(parseInt(opts.limit ?? "20") || 20, 1), 50);
|
|
25772
|
+
const spin = ora("Loading history...").start();
|
|
25773
|
+
try {
|
|
25774
|
+
const res = await api(`/api/nanobanana2/history?limit=${limit}`);
|
|
25775
|
+
spin.stop();
|
|
25776
|
+
if (res.data.items.length === 0) {
|
|
25777
|
+
console.log(source_default.dim("No generations yet."));
|
|
25778
|
+
return;
|
|
25779
|
+
}
|
|
25780
|
+
for (const item of res.data.items) {
|
|
25781
|
+
const when = new Date(item.createdAt).toLocaleString();
|
|
25782
|
+
const truncated = item.prompt.length > 80 ? item.prompt.slice(0, 77) + "..." : item.prompt;
|
|
25783
|
+
console.log(
|
|
25784
|
+
`${source_default.dim(item.id)} ${source_default.cyan(item.status.padEnd(10))} ${source_default.bold(`${item.imageCount} img`)} ${source_default.dim(when)}`
|
|
25785
|
+
);
|
|
25786
|
+
console.log(" " + truncated);
|
|
25787
|
+
}
|
|
25788
|
+
} catch (err) {
|
|
25789
|
+
spin.fail(source_default.red(`History failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
25790
|
+
process.exitCode = 1;
|
|
25791
|
+
}
|
|
25792
|
+
}
|
|
25793
|
+
function registerImage(program3) {
|
|
25794
|
+
const cmd = program3.command("image").alias("img").description("Generate images via Launchmatic (NanoBanana2 / Gemini 2.5 Flash Image)");
|
|
25795
|
+
cmd.command("generate <prompt...>").alias("gen").description('Generate images from a prompt. Usage: lm image gen "watercolor mountain"').option("-c, --count <n>", "How many images (1\u20134)", "1").option("-a, --aspect-ratio <ratio>", "1:1, 16:9, 9:16, 4:3, or 3:4", "1:1").option("-o, --out-dir <dir>", "Where to write files (default: cwd)").option("-n, --name <name>", "Base filename (default: derived from prompt)").action(generateCmd);
|
|
25796
|
+
cmd.command("usage").description("Show current image/token quota").action(usageCmd);
|
|
25797
|
+
cmd.command("history").description("List recent image generations (metadata only)").option("-l, --limit <n>", "Max entries to show (1\u201350)", "20").action(historyCmd);
|
|
25798
|
+
}
|
|
25799
|
+
|
|
25800
|
+
// src/commands/monorepo.ts
|
|
25801
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
25802
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
25803
|
+
import { join as join5 } from "path";
|
|
25804
|
+
import readline7 from "readline";
|
|
25805
|
+
|
|
25806
|
+
// src/monorepo.ts
|
|
25807
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync3, statSync as statSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
25808
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
25809
|
+
import { join as join4, relative as relative2, sep, posix } from "path";
|
|
25810
|
+
var MANIFEST_FILE = "launchmatic.json";
|
|
25811
|
+
function findRepoRoot(start = process.cwd()) {
|
|
25812
|
+
try {
|
|
25813
|
+
return execFileSync8("git", ["rev-parse", "--show-toplevel"], {
|
|
25814
|
+
cwd: start,
|
|
25815
|
+
encoding: "utf-8",
|
|
25816
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
25817
|
+
}).trim();
|
|
25818
|
+
} catch {
|
|
25819
|
+
return start;
|
|
25820
|
+
}
|
|
25821
|
+
}
|
|
25822
|
+
function manifestPath(repoRoot = findRepoRoot()) {
|
|
25823
|
+
return join4(repoRoot, MANIFEST_FILE);
|
|
25824
|
+
}
|
|
25825
|
+
function readManifest(repoRoot = findRepoRoot()) {
|
|
25826
|
+
const p = manifestPath(repoRoot);
|
|
25827
|
+
if (!existsSync6(p)) return null;
|
|
25828
|
+
try {
|
|
25829
|
+
const parsed = JSON.parse(readFileSync6(p, "utf-8"));
|
|
25830
|
+
if (parsed.version !== 1 || !Array.isArray(parsed.services)) {
|
|
25831
|
+
throw new Error(`${MANIFEST_FILE} has unexpected shape`);
|
|
25832
|
+
}
|
|
25833
|
+
return parsed;
|
|
25834
|
+
} catch (err) {
|
|
25835
|
+
throw new Error(`Could not read ${MANIFEST_FILE}: ${err instanceof Error ? err.message : String(err)}`);
|
|
25836
|
+
}
|
|
25837
|
+
}
|
|
25838
|
+
function writeManifest(manifest, repoRoot = findRepoRoot()) {
|
|
25839
|
+
writeFileSync3(manifestPath(repoRoot), JSON.stringify(manifest, null, 2) + "\n");
|
|
25840
|
+
}
|
|
25841
|
+
function discoverServices(repoRoot = findRepoRoot()) {
|
|
25842
|
+
const globs = readWorkspaceGlobs(repoRoot);
|
|
25843
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
25844
|
+
for (const glob of globs) {
|
|
25845
|
+
for (const dir of expandGlob(repoRoot, glob)) {
|
|
25846
|
+
dirs.add(dir);
|
|
25847
|
+
}
|
|
25848
|
+
}
|
|
25849
|
+
if (globs.length === 0) {
|
|
25850
|
+
for (const conv of ["apps", "services"]) {
|
|
25851
|
+
const base = join4(repoRoot, conv);
|
|
25852
|
+
if (existsSync6(base) && statSync2(base).isDirectory()) {
|
|
25853
|
+
for (const entry of readdirSync3(base)) {
|
|
25854
|
+
const full = join4(base, entry);
|
|
25855
|
+
if (statSync2(full).isDirectory()) dirs.add(full);
|
|
25856
|
+
}
|
|
25857
|
+
}
|
|
25858
|
+
}
|
|
25859
|
+
}
|
|
25860
|
+
const out = [];
|
|
25861
|
+
for (const absDir of dirs) {
|
|
25862
|
+
if (!isLikelyDeployable(absDir)) continue;
|
|
25863
|
+
const detection = detectLocal(absDir);
|
|
25864
|
+
out.push({
|
|
25865
|
+
name: deriveName(repoRoot, absDir),
|
|
25866
|
+
rootDir: toPosix(relative2(repoRoot, absDir)),
|
|
25867
|
+
framework: detection.framework,
|
|
25868
|
+
buildCmd: detection.buildCmd,
|
|
25869
|
+
startCmd: detection.startCmd,
|
|
25870
|
+
port: detection.port
|
|
25871
|
+
});
|
|
25872
|
+
}
|
|
25873
|
+
out.sort((a, b) => a.rootDir.localeCompare(b.rootDir));
|
|
25874
|
+
return out;
|
|
25875
|
+
}
|
|
25876
|
+
function readWorkspaceGlobs(repoRoot) {
|
|
25877
|
+
const globs = [];
|
|
25878
|
+
const pnpmFile = join4(repoRoot, "pnpm-workspace.yaml");
|
|
25879
|
+
if (existsSync6(pnpmFile)) {
|
|
25880
|
+
const text = readFileSync6(pnpmFile, "utf-8");
|
|
25881
|
+
let inPackages = false;
|
|
25882
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
25883
|
+
const line = rawLine.replace(/#.*$/, "").trimEnd();
|
|
25884
|
+
if (/^packages\s*:/i.test(line)) {
|
|
25885
|
+
inPackages = true;
|
|
25886
|
+
continue;
|
|
25887
|
+
}
|
|
25888
|
+
if (inPackages) {
|
|
25889
|
+
const m = line.match(/^\s*-\s*['"]?([^'"#]+?)['"]?\s*$/);
|
|
25890
|
+
if (m) {
|
|
25891
|
+
globs.push(m[1].trim());
|
|
25892
|
+
continue;
|
|
25893
|
+
}
|
|
25894
|
+
if (line.trim() && !line.startsWith(" ") && !line.startsWith(" ")) {
|
|
25895
|
+
inPackages = false;
|
|
25896
|
+
}
|
|
25897
|
+
}
|
|
25898
|
+
}
|
|
25899
|
+
}
|
|
25900
|
+
const pkgFile = join4(repoRoot, "package.json");
|
|
25901
|
+
if (existsSync6(pkgFile)) {
|
|
25902
|
+
try {
|
|
25903
|
+
const pkg = JSON.parse(readFileSync6(pkgFile, "utf-8"));
|
|
25904
|
+
if (Array.isArray(pkg.workspaces)) {
|
|
25905
|
+
globs.push(...pkg.workspaces.filter((g) => typeof g === "string"));
|
|
25906
|
+
} else if (pkg.workspaces && Array.isArray(pkg.workspaces.packages)) {
|
|
25907
|
+
globs.push(...pkg.workspaces.packages.filter((g) => typeof g === "string"));
|
|
25908
|
+
}
|
|
25909
|
+
} catch {
|
|
25910
|
+
}
|
|
25911
|
+
}
|
|
25912
|
+
return globs;
|
|
25913
|
+
}
|
|
25914
|
+
function expandGlob(repoRoot, glob) {
|
|
25915
|
+
const cleaned = glob.replace(/^\.\//, "").replace(/\/$/, "");
|
|
25916
|
+
if (cleaned.endsWith("/*")) {
|
|
25917
|
+
const base = join4(repoRoot, cleaned.slice(0, -2));
|
|
25918
|
+
if (!existsSync6(base) || !statSync2(base).isDirectory()) return [];
|
|
25919
|
+
return readdirSync3(base).map((entry) => join4(base, entry)).filter((p) => {
|
|
25920
|
+
try {
|
|
25921
|
+
return statSync2(p).isDirectory();
|
|
25922
|
+
} catch {
|
|
25923
|
+
return false;
|
|
25924
|
+
}
|
|
25925
|
+
});
|
|
25926
|
+
}
|
|
25927
|
+
const abs = join4(repoRoot, cleaned);
|
|
25928
|
+
if (existsSync6(abs) && statSync2(abs).isDirectory()) return [abs];
|
|
25929
|
+
return [];
|
|
25930
|
+
}
|
|
25931
|
+
function isLikelyDeployable(absDir) {
|
|
25932
|
+
if (existsSync6(join4(absDir, "Dockerfile"))) return true;
|
|
25933
|
+
const pkgPath = join4(absDir, "package.json");
|
|
25934
|
+
if (existsSync6(pkgPath)) {
|
|
25935
|
+
try {
|
|
25936
|
+
const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
25937
|
+
if (pkg.scripts?.start || pkg.scripts?.dev || pkg.scripts?.serve) return true;
|
|
25938
|
+
if (pkg.bin) return true;
|
|
25939
|
+
} catch {
|
|
25940
|
+
}
|
|
25941
|
+
}
|
|
25942
|
+
if (existsSync6(join4(absDir, "next.config.js")) || existsSync6(join4(absDir, "next.config.mjs")) || existsSync6(join4(absDir, "next.config.ts")) || existsSync6(join4(absDir, "go.mod")) || existsSync6(join4(absDir, "Cargo.toml")) || existsSync6(join4(absDir, "manage.py")) || existsSync6(join4(absDir, "pyproject.toml")) || existsSync6(join4(absDir, "Gemfile"))) {
|
|
25943
|
+
return true;
|
|
25944
|
+
}
|
|
25945
|
+
return false;
|
|
25946
|
+
}
|
|
25947
|
+
function deriveName(repoRoot, absDir) {
|
|
25948
|
+
const rel = toPosix(relative2(repoRoot, absDir));
|
|
25949
|
+
const parts = rel.split("/");
|
|
25950
|
+
return parts[parts.length - 1].toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
25951
|
+
}
|
|
25952
|
+
function toPosix(p) {
|
|
25953
|
+
return p.split(sep).join(posix.sep);
|
|
25954
|
+
}
|
|
25955
|
+
function changedFilesSince(repoRoot, baseRef) {
|
|
25956
|
+
const ref = baseRef ?? autoBaseRef(repoRoot);
|
|
25957
|
+
if (!ref) return [];
|
|
25958
|
+
try {
|
|
25959
|
+
const out = execFileSync8("git", ["diff", "--name-only", `${ref}..HEAD`], {
|
|
25960
|
+
cwd: repoRoot,
|
|
25961
|
+
encoding: "utf-8",
|
|
25962
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
25963
|
+
});
|
|
25964
|
+
return out.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
25965
|
+
} catch {
|
|
25966
|
+
return [];
|
|
25967
|
+
}
|
|
25968
|
+
}
|
|
25969
|
+
function autoBaseRef(repoRoot) {
|
|
25970
|
+
try {
|
|
25971
|
+
const branch = execFileSync8("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
25972
|
+
cwd: repoRoot,
|
|
25973
|
+
encoding: "utf-8",
|
|
25974
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
25975
|
+
}).trim();
|
|
25976
|
+
if (branch && branch !== "HEAD") {
|
|
25977
|
+
try {
|
|
25978
|
+
execFileSync8("git", ["rev-parse", "--verify", `origin/${branch}`], {
|
|
25979
|
+
cwd: repoRoot,
|
|
25980
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
25981
|
+
});
|
|
25982
|
+
return `origin/${branch}`;
|
|
25983
|
+
} catch {
|
|
25984
|
+
}
|
|
25985
|
+
}
|
|
25986
|
+
} catch {
|
|
25987
|
+
}
|
|
25988
|
+
try {
|
|
25989
|
+
execFileSync8("git", ["rev-parse", "--verify", "HEAD~1"], {
|
|
25990
|
+
cwd: repoRoot,
|
|
25991
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
25992
|
+
});
|
|
25993
|
+
return "HEAD~1";
|
|
25994
|
+
} catch {
|
|
25995
|
+
return null;
|
|
25996
|
+
}
|
|
25997
|
+
}
|
|
25998
|
+
function serviceWasChanged(rootDir, changedPaths) {
|
|
25999
|
+
if (changedPaths.length === 0) return true;
|
|
26000
|
+
const normalized = rootDir.replace(/\\/g, "/").replace(/^\.?\//, "").replace(/\/$/, "");
|
|
26001
|
+
if (normalized === "" || normalized === "." || normalized === "/") return true;
|
|
26002
|
+
const prefix = normalized + "/";
|
|
26003
|
+
return changedPaths.some((p) => p === normalized || p.startsWith(prefix));
|
|
26004
|
+
}
|
|
26005
|
+
|
|
26006
|
+
// src/commands/monorepo.ts
|
|
26007
|
+
function requireLogin3() {
|
|
26008
|
+
if (!isLoggedIn()) {
|
|
26009
|
+
console.error(source_default.red('Not logged in. Run "lm login" first.'));
|
|
26010
|
+
process.exitCode = 1;
|
|
26011
|
+
return false;
|
|
26012
|
+
}
|
|
26013
|
+
return true;
|
|
26014
|
+
}
|
|
26015
|
+
function prompt6(question) {
|
|
26016
|
+
const rl = readline7.createInterface({ input: process.stdin, output: process.stdout });
|
|
26017
|
+
return new Promise((resolve5) => {
|
|
26018
|
+
rl.question(question, (a) => {
|
|
26019
|
+
rl.close();
|
|
26020
|
+
resolve5(a.trim());
|
|
26021
|
+
});
|
|
26022
|
+
});
|
|
26023
|
+
}
|
|
26024
|
+
async function initManifest(opts) {
|
|
26025
|
+
if (!requireLogin3()) return;
|
|
26026
|
+
const repoRoot = findRepoRoot();
|
|
26027
|
+
const path7 = manifestPath(repoRoot);
|
|
26028
|
+
if (existsSync7(path7)) {
|
|
26029
|
+
console.error(source_default.yellow(`${MANIFEST_FILE} already exists at ${path7}`));
|
|
26030
|
+
console.error(source_default.dim("Edit it manually or delete and re-run."));
|
|
26031
|
+
process.exitCode = 1;
|
|
26032
|
+
return;
|
|
26033
|
+
}
|
|
26034
|
+
const spin = ora("Discovering services...").start();
|
|
26035
|
+
const discovered = discoverServices(repoRoot);
|
|
26036
|
+
spin.stop();
|
|
26037
|
+
if (discovered.length === 0) {
|
|
26038
|
+
console.error(source_default.red("No services discovered."));
|
|
26039
|
+
console.error(source_default.dim("Looked for: pnpm-workspace.yaml, package.json#workspaces, apps/*, services/*"));
|
|
26040
|
+
console.error(source_default.dim("If your services live elsewhere, create launchmatic.json by hand."));
|
|
26041
|
+
process.exitCode = 1;
|
|
26042
|
+
return;
|
|
26043
|
+
}
|
|
26044
|
+
console.log(source_default.bold(`
|
|
26045
|
+
Discovered ${discovered.length} service${discovered.length === 1 ? "" : "s"}:`));
|
|
26046
|
+
for (const s of discovered) {
|
|
26047
|
+
const fw = s.framework ? source_default.dim(` [${s.framework}]`) : "";
|
|
26048
|
+
console.log(` ${source_default.cyan(s.name)} ${source_default.dim(s.rootDir)}${fw}`);
|
|
26049
|
+
}
|
|
26050
|
+
const { data: teams } = await api("/api/teams");
|
|
26051
|
+
if (!teams || teams.length === 0) {
|
|
26052
|
+
console.error(source_default.red("No teams found on your account."));
|
|
26053
|
+
process.exitCode = 1;
|
|
26054
|
+
return;
|
|
26055
|
+
}
|
|
26056
|
+
let teamId = opts.team;
|
|
26057
|
+
if (!teamId) {
|
|
26058
|
+
if (teams.length === 1 || opts.yes) {
|
|
26059
|
+
teamId = teams[0].id;
|
|
26060
|
+
} else {
|
|
26061
|
+
console.log(source_default.bold("\nTeams:"));
|
|
26062
|
+
teams.forEach((t, i) => console.log(` ${i + 1}. ${t.name} ${source_default.dim(`(${t.slug})`)}`));
|
|
26063
|
+
const ans = await prompt6("Pick a team (number): ");
|
|
26064
|
+
teamId = teams[parseInt(ans) - 1]?.id ?? teams[0].id;
|
|
26065
|
+
}
|
|
26066
|
+
}
|
|
26067
|
+
const projectName = opts.project || opts.name || (existsSync7(join5(repoRoot, "package.json")) ? (() => {
|
|
26068
|
+
try {
|
|
26069
|
+
const pkg = JSON.parse(readFileSync7(join5(repoRoot, "package.json"), "utf-8"));
|
|
26070
|
+
return pkg.name?.replace(/^@.*\//, "") || "monorepo";
|
|
26071
|
+
} catch {
|
|
26072
|
+
return "monorepo";
|
|
26073
|
+
}
|
|
26074
|
+
})() : "monorepo");
|
|
26075
|
+
const projectSlug = projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "monorepo";
|
|
26076
|
+
const { data: existingProjects } = await api(
|
|
26077
|
+
`/api/projects?teamId=${teamId}`
|
|
26078
|
+
);
|
|
26079
|
+
let projectId = existingProjects.find((p) => p.slug === projectSlug)?.id;
|
|
26080
|
+
if (!projectId) {
|
|
26081
|
+
const create = await api("/api/projects", {
|
|
26082
|
+
method: "POST",
|
|
26083
|
+
body: JSON.stringify({ name: projectName, slug: projectSlug, teamId })
|
|
26084
|
+
});
|
|
26085
|
+
projectId = create.data.id;
|
|
26086
|
+
console.log(source_default.green(`Created project ${source_default.bold(projectName)}`));
|
|
26087
|
+
} else {
|
|
26088
|
+
console.log(source_default.dim(`Using existing project ${projectName}`));
|
|
26089
|
+
}
|
|
26090
|
+
const { data: existingServices } = await api(`/api/services?projectId=${projectId}`);
|
|
26091
|
+
const gitInfo = getGitInfo(repoRoot);
|
|
26092
|
+
const services = [];
|
|
26093
|
+
for (const s of discovered) {
|
|
26094
|
+
const slug = s.name;
|
|
26095
|
+
let svcId = existingServices.find((es) => es.slug === slug || es.rootDir === `/${s.rootDir}` || es.rootDir === s.rootDir)?.id;
|
|
26096
|
+
if (!svcId) {
|
|
26097
|
+
const payload = {
|
|
26098
|
+
name: s.name,
|
|
26099
|
+
slug,
|
|
26100
|
+
type: "WEB",
|
|
26101
|
+
projectId,
|
|
26102
|
+
rootDir: `/${s.rootDir}`,
|
|
26103
|
+
port: s.port
|
|
26104
|
+
};
|
|
26105
|
+
if (s.framework) payload.framework = s.framework;
|
|
26106
|
+
if (s.buildCmd) payload.buildCmd = s.buildCmd;
|
|
26107
|
+
if (s.startCmd) payload.startCmd = s.startCmd;
|
|
26108
|
+
if (gitInfo.repoOwner && gitInfo.repoName) {
|
|
26109
|
+
payload.repoOwner = gitInfo.repoOwner;
|
|
26110
|
+
payload.repoName = gitInfo.repoName;
|
|
26111
|
+
payload.repoBranch = gitInfo.repoBranch;
|
|
26112
|
+
payload.repoUrl = `https://github.com/${gitInfo.repoOwner}/${gitInfo.repoName}.git`;
|
|
26113
|
+
}
|
|
26114
|
+
try {
|
|
26115
|
+
const created = await api("/api/services", {
|
|
26116
|
+
method: "POST",
|
|
26117
|
+
body: JSON.stringify(payload)
|
|
26118
|
+
});
|
|
26119
|
+
svcId = created.data.id;
|
|
26120
|
+
console.log(source_default.green(`Created service ${source_default.bold(s.name)}`));
|
|
26121
|
+
} catch (err) {
|
|
26122
|
+
console.warn(source_default.yellow(`Could not create service ${s.name}: ${err instanceof Error ? err.message : String(err)}`));
|
|
26123
|
+
continue;
|
|
26124
|
+
}
|
|
26125
|
+
} else {
|
|
26126
|
+
console.log(source_default.dim(`Using existing service ${s.name}`));
|
|
26127
|
+
}
|
|
26128
|
+
services.push({
|
|
26129
|
+
name: s.name,
|
|
26130
|
+
rootDir: s.rootDir,
|
|
26131
|
+
serviceId: svcId,
|
|
26132
|
+
framework: s.framework,
|
|
26133
|
+
buildCmd: s.buildCmd,
|
|
26134
|
+
startCmd: s.startCmd,
|
|
26135
|
+
port: s.port
|
|
26136
|
+
});
|
|
26137
|
+
}
|
|
26138
|
+
const manifest = {
|
|
26139
|
+
version: 1,
|
|
26140
|
+
teamId,
|
|
26141
|
+
projectId,
|
|
26142
|
+
services
|
|
26143
|
+
};
|
|
26144
|
+
writeManifest(manifest, repoRoot);
|
|
26145
|
+
console.log();
|
|
26146
|
+
console.log(source_default.green(`\u2713 Wrote ${source_default.bold(MANIFEST_FILE)} (${services.length} services)`));
|
|
26147
|
+
console.log(source_default.dim("Next: ") + source_default.bold("lm up") + source_default.dim(" to deploy everything."));
|
|
26148
|
+
}
|
|
26149
|
+
async function listManifest() {
|
|
26150
|
+
const manifest = readManifest();
|
|
26151
|
+
if (!manifest) {
|
|
26152
|
+
console.error(source_default.red(`No ${MANIFEST_FILE} in this repo. Run ${source_default.bold("lm monorepo init")}.`));
|
|
26153
|
+
process.exitCode = 1;
|
|
26154
|
+
return;
|
|
26155
|
+
}
|
|
26156
|
+
console.log(source_default.bold(`${manifest.services.length} service${manifest.services.length === 1 ? "" : "s"}:`));
|
|
26157
|
+
for (const s of manifest.services) {
|
|
26158
|
+
const fw = s.framework ? source_default.dim(` [${s.framework}]`) : "";
|
|
26159
|
+
const sid = s.serviceId ? source_default.dim(` ${s.serviceId}`) : source_default.yellow(" (not yet created)");
|
|
26160
|
+
console.log(` ${source_default.cyan(s.name.padEnd(16))} ${source_default.dim(s.rootDir)}${fw}${sid}`);
|
|
26161
|
+
}
|
|
26162
|
+
}
|
|
26163
|
+
async function up(opts) {
|
|
26164
|
+
if (!requireLogin3()) return;
|
|
26165
|
+
const repoRoot = findRepoRoot();
|
|
26166
|
+
const manifest = readManifest(repoRoot);
|
|
26167
|
+
if (!manifest) {
|
|
26168
|
+
console.error(source_default.red(`No ${MANIFEST_FILE} found. Run ${source_default.bold("lm monorepo init")} first.`));
|
|
26169
|
+
process.exitCode = 1;
|
|
26170
|
+
return;
|
|
26171
|
+
}
|
|
26172
|
+
const gitInfo = getGitInfo(repoRoot);
|
|
26173
|
+
if (gitInfo.hasUnpushed && gitInfo.hasRemote) {
|
|
26174
|
+
const pushSpin = ora("Pushing commits to remote...").start();
|
|
26175
|
+
try {
|
|
26176
|
+
execFileSync9("git", ["push", "origin", gitInfo.repoBranch], {
|
|
26177
|
+
cwd: repoRoot,
|
|
26178
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
26179
|
+
});
|
|
26180
|
+
pushSpin.succeed("Pushed commits to remote");
|
|
26181
|
+
} catch (err) {
|
|
26182
|
+
pushSpin.fail(`Push failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
26183
|
+
process.exitCode = 1;
|
|
26184
|
+
return;
|
|
26185
|
+
}
|
|
26186
|
+
}
|
|
26187
|
+
const wantNames = opts.service && opts.service.length > 0 ? new Set(opts.service) : null;
|
|
26188
|
+
const changed = opts.all ? [] : changedFilesSince(repoRoot, opts.base);
|
|
26189
|
+
if (!opts.all && changed.length > 0) {
|
|
26190
|
+
console.log(source_default.dim(`Diff base: ${opts.base ?? "auto"} \u2014 ${changed.length} file${changed.length === 1 ? "" : "s"} changed`));
|
|
26191
|
+
}
|
|
26192
|
+
const selected = [];
|
|
26193
|
+
const skipped = [];
|
|
26194
|
+
for (const s of manifest.services) {
|
|
26195
|
+
if (wantNames && !wantNames.has(s.name)) {
|
|
26196
|
+
skipped.push({ name: s.name, reason: "not in --service filter" });
|
|
26197
|
+
continue;
|
|
26198
|
+
}
|
|
26199
|
+
if (!s.serviceId) {
|
|
26200
|
+
skipped.push({ name: s.name, reason: "no serviceId \u2014 re-run lm monorepo init" });
|
|
26201
|
+
continue;
|
|
26202
|
+
}
|
|
26203
|
+
if (!opts.all && changed.length > 0 && !serviceWasChanged(s.rootDir, changed)) {
|
|
26204
|
+
skipped.push({ name: s.name, reason: "no changes in rootDir" });
|
|
26205
|
+
continue;
|
|
26206
|
+
}
|
|
26207
|
+
selected.push({ entry: s, reason: opts.all ? "--all" : changed.length === 0 ? "no diff base" : "rootDir touched" });
|
|
26208
|
+
}
|
|
26209
|
+
if (selected.length === 0) {
|
|
26210
|
+
console.log(source_default.yellow("Nothing to deploy."));
|
|
26211
|
+
for (const s of skipped) console.log(source_default.dim(` - ${s.name}: ${s.reason}`));
|
|
26212
|
+
console.log(source_default.dim(`Force a deploy of all services with ${source_default.bold("lm up --all")}.`));
|
|
26213
|
+
return;
|
|
26214
|
+
}
|
|
26215
|
+
console.log(source_default.bold(`Deploying ${selected.length} service${selected.length === 1 ? "" : "s"}:`));
|
|
26216
|
+
for (const s of selected) console.log(` ${source_default.cyan(s.entry.name)} ${source_default.dim(`(${s.reason})`)}`);
|
|
26217
|
+
if (skipped.length > 0) {
|
|
26218
|
+
console.log(source_default.dim(`Skipped:`));
|
|
26219
|
+
for (const s of skipped) console.log(source_default.dim(` - ${s.name}: ${s.reason}`));
|
|
26220
|
+
}
|
|
26221
|
+
console.log();
|
|
26222
|
+
const results = await Promise.allSettled(
|
|
26223
|
+
selected.map(async ({ entry }) => {
|
|
26224
|
+
const res = await api("/api/deployments", {
|
|
26225
|
+
method: "POST",
|
|
26226
|
+
body: JSON.stringify({
|
|
26227
|
+
serviceId: entry.serviceId,
|
|
26228
|
+
branch: gitInfo.repoBranch,
|
|
26229
|
+
commitSha: gitInfo.commitSha
|
|
26230
|
+
})
|
|
26231
|
+
});
|
|
26232
|
+
return { name: entry.name, deploymentId: res.data.id };
|
|
26233
|
+
})
|
|
26234
|
+
);
|
|
26235
|
+
let okCount = 0;
|
|
26236
|
+
let failCount = 0;
|
|
26237
|
+
for (let i = 0; i < results.length; i++) {
|
|
26238
|
+
const r = results[i];
|
|
26239
|
+
const name = selected[i].entry.name;
|
|
26240
|
+
if (r.status === "fulfilled") {
|
|
26241
|
+
okCount++;
|
|
26242
|
+
console.log(source_default.green(`\u2713 ${name}`) + source_default.dim(` queued (deployment ${r.value.deploymentId})`));
|
|
26243
|
+
} else {
|
|
26244
|
+
failCount++;
|
|
26245
|
+
const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);
|
|
26246
|
+
console.log(source_default.red(`\u2717 ${name}`) + source_default.dim(` ${msg}`));
|
|
26247
|
+
}
|
|
26248
|
+
}
|
|
26249
|
+
console.log();
|
|
26250
|
+
console.log(
|
|
26251
|
+
source_default.bold(`Deployed ${okCount}/${selected.length}`) + source_default.dim(` Track progress: lm status / lm logs --service <name>`)
|
|
26252
|
+
);
|
|
26253
|
+
if (failCount > 0) process.exitCode = 1;
|
|
26254
|
+
}
|
|
26255
|
+
function registerMonorepo(program3) {
|
|
26256
|
+
const cmd = program3.command("monorepo").alias("mono").description("Manage the launchmatic.json manifest for multi-service repos");
|
|
26257
|
+
cmd.command("init").description("Auto-discover services and write launchmatic.json").option("-y, --yes", "Don't prompt \u2014 accept all defaults").option("--team <id>", "Team ID to attach the project to").option("--project <name>", "Project name (default: derived from repo)").option("-n, --name <name>", "Alias for --project").action(initManifest);
|
|
26258
|
+
cmd.command("list").alias("ls").description("Print services declared in launchmatic.json").action(listManifest);
|
|
26259
|
+
}
|
|
26260
|
+
function registerUp(program3) {
|
|
26261
|
+
program3.command("up").description("Deploy every service in launchmatic.json (Railway-style multi-service deploy)").option("-s, --service <name...>", "Only deploy these services (by name)").option("-a, --all", "Skip the changed-only filter \u2014 deploy every service").option("--base <ref>", "Git ref to diff against (default: origin/<branch> or HEAD~1)").action(up);
|
|
26262
|
+
}
|
|
26263
|
+
|
|
26264
|
+
// src/commands/template.ts
|
|
26265
|
+
import readline8 from "readline";
|
|
26266
|
+
function requireLogin4() {
|
|
26267
|
+
if (!isLoggedIn()) {
|
|
26268
|
+
console.error(source_default.red('Not logged in. Run "lm login" first.'));
|
|
26269
|
+
process.exitCode = 1;
|
|
26270
|
+
return false;
|
|
26271
|
+
}
|
|
26272
|
+
return true;
|
|
26273
|
+
}
|
|
26274
|
+
function prompt7(question) {
|
|
26275
|
+
const rl = readline8.createInterface({ input: process.stdin, output: process.stdout });
|
|
26276
|
+
return new Promise((resolve5) => {
|
|
26277
|
+
rl.question(question, (a) => {
|
|
26278
|
+
rl.close();
|
|
26279
|
+
resolve5(a.trim());
|
|
26280
|
+
});
|
|
26281
|
+
});
|
|
26282
|
+
}
|
|
26283
|
+
async function listCmd(opts) {
|
|
26284
|
+
const spin = ora("Loading templates...").start();
|
|
26285
|
+
try {
|
|
26286
|
+
const res = await api("/api/templates");
|
|
26287
|
+
spin.stop();
|
|
26288
|
+
const filtered = opts.category ? res.data.filter((t) => t.category === opts.category.toLowerCase()) : res.data;
|
|
26289
|
+
if (filtered.length === 0) {
|
|
26290
|
+
console.log(source_default.dim("No templates match."));
|
|
26291
|
+
return;
|
|
26292
|
+
}
|
|
26293
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
26294
|
+
for (const t of filtered) {
|
|
26295
|
+
const arr = byCategory.get(t.category) ?? [];
|
|
26296
|
+
arr.push(t);
|
|
26297
|
+
byCategory.set(t.category, arr);
|
|
26298
|
+
}
|
|
26299
|
+
for (const [cat, templates] of byCategory) {
|
|
26300
|
+
console.log();
|
|
26301
|
+
console.log(source_default.bold.underline(cat));
|
|
26302
|
+
for (const t of templates) {
|
|
26303
|
+
console.log(` ${source_default.cyan(t.slug.padEnd(20))} ${source_default.bold(t.name)}`);
|
|
26304
|
+
console.log(` ${source_default.dim(" ".repeat(20))} ${source_default.dim(t.description)}`);
|
|
26305
|
+
const chips = [...t.tags];
|
|
26306
|
+
if (t.databases.length > 0) chips.push(...t.databases.map((d) => `db:${d}`));
|
|
26307
|
+
if (chips.length > 0) console.log(` ${source_default.dim(" ".repeat(20))} ${source_default.dim(chips.join(" \xB7 "))}`);
|
|
26308
|
+
}
|
|
26309
|
+
}
|
|
26310
|
+
console.log();
|
|
26311
|
+
console.log(source_default.dim(`Deploy with: ${source_default.bold("lm template deploy <slug>")}`));
|
|
26312
|
+
} catch (err) {
|
|
26313
|
+
spin.fail(`Could not load templates: ${err instanceof Error ? err.message : String(err)}`);
|
|
26314
|
+
process.exitCode = 1;
|
|
26315
|
+
}
|
|
26316
|
+
}
|
|
26317
|
+
async function deployCmd(slug, opts) {
|
|
26318
|
+
if (!requireLogin4()) return;
|
|
26319
|
+
let detail;
|
|
26320
|
+
try {
|
|
26321
|
+
const res = await api(`/api/templates/${slug}`);
|
|
26322
|
+
detail = res.data;
|
|
26323
|
+
} catch (err) {
|
|
26324
|
+
console.error(source_default.red(`Could not find template "${slug}": ${err instanceof Error ? err.message : String(err)}`));
|
|
26325
|
+
process.exitCode = 1;
|
|
26326
|
+
return;
|
|
26327
|
+
}
|
|
26328
|
+
let projectId = opts.project;
|
|
26329
|
+
if (!projectId && contextExists()) {
|
|
26330
|
+
try {
|
|
26331
|
+
projectId = readContext().projectId;
|
|
26332
|
+
} catch {
|
|
26333
|
+
}
|
|
26334
|
+
}
|
|
26335
|
+
if (!projectId) {
|
|
26336
|
+
const { data: teams } = await api("/api/teams");
|
|
26337
|
+
const teamId = teams[0]?.id;
|
|
26338
|
+
if (!teamId) {
|
|
26339
|
+
console.error(source_default.red("No teams found on your account."));
|
|
26340
|
+
process.exitCode = 1;
|
|
26341
|
+
return;
|
|
26342
|
+
}
|
|
26343
|
+
const { data: projects } = await api(
|
|
26344
|
+
`/api/projects?teamId=${teamId}`
|
|
26345
|
+
);
|
|
26346
|
+
if (projects.length === 0) {
|
|
26347
|
+
console.error(source_default.red("No projects yet. Pass --project <projectId> or create one in the dashboard."));
|
|
26348
|
+
process.exitCode = 1;
|
|
26349
|
+
return;
|
|
26350
|
+
}
|
|
26351
|
+
if (projects.length === 1 || opts.yes) {
|
|
26352
|
+
projectId = projects[0].id;
|
|
26353
|
+
} else {
|
|
26354
|
+
console.log(source_default.bold("Pick a project:"));
|
|
26355
|
+
projects.forEach((p, i) => console.log(` ${i + 1}. ${p.name} ${source_default.dim(`(${p.slug})`)}`));
|
|
26356
|
+
const ans = await prompt7("Project number: ");
|
|
26357
|
+
projectId = projects[parseInt(ans) - 1]?.id ?? projects[0].id;
|
|
26358
|
+
}
|
|
26359
|
+
}
|
|
26360
|
+
const envVars = {};
|
|
26361
|
+
for (const pair of opts.env ?? []) {
|
|
26362
|
+
const eq = pair.indexOf("=");
|
|
26363
|
+
if (eq < 0) {
|
|
26364
|
+
console.error(source_default.red(`--env expects KEY=value, got ${pair}`));
|
|
26365
|
+
process.exitCode = 1;
|
|
26366
|
+
return;
|
|
26367
|
+
}
|
|
26368
|
+
envVars[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
26369
|
+
}
|
|
26370
|
+
for (const v of detail.envVars.filter((e) => e.required)) {
|
|
26371
|
+
if (envVars[v.key]) continue;
|
|
26372
|
+
if (opts.yes) {
|
|
26373
|
+
console.error(source_default.red(`--yes set but required env var ${v.key} missing.`));
|
|
26374
|
+
process.exitCode = 1;
|
|
26375
|
+
return;
|
|
26376
|
+
}
|
|
26377
|
+
console.log(source_default.dim(`
|
|
26378
|
+
${v.description}`));
|
|
26379
|
+
const example = v.example ? source_default.dim(` (e.g. ${v.example})`) : "";
|
|
26380
|
+
const ans = await prompt7(`${source_default.cyan(v.key)}${example}: `);
|
|
26381
|
+
if (!ans) {
|
|
26382
|
+
console.error(source_default.red(`Required env var ${v.key} cannot be empty.`));
|
|
26383
|
+
process.exitCode = 1;
|
|
26384
|
+
return;
|
|
26385
|
+
}
|
|
26386
|
+
envVars[v.key] = ans;
|
|
26387
|
+
}
|
|
26388
|
+
console.log();
|
|
26389
|
+
console.log(source_default.bold(`Deploying template ${source_default.cyan(detail.slug)}: ${detail.name}`));
|
|
26390
|
+
if (detail.databases.length > 0) {
|
|
26391
|
+
console.log(source_default.dim(` + ${detail.databases.join(", ")} ${detail.databases.length === 1 ? "database" : "databases"}`));
|
|
26392
|
+
}
|
|
26393
|
+
console.log();
|
|
26394
|
+
const spin = ora("Generating starter code...").start();
|
|
26395
|
+
try {
|
|
26396
|
+
const res = await api(`/api/templates/${slug}/deploy`, {
|
|
26397
|
+
method: "POST",
|
|
26398
|
+
body: JSON.stringify({
|
|
26399
|
+
projectId,
|
|
26400
|
+
name: opts.name,
|
|
26401
|
+
envVars
|
|
26402
|
+
})
|
|
26403
|
+
});
|
|
26404
|
+
spin.succeed(source_default.green("Template queued"));
|
|
26405
|
+
console.log();
|
|
26406
|
+
console.log(` Service ID: ${source_default.cyan(res.data.serviceId)}`);
|
|
26407
|
+
console.log(` Generation ID: ${source_default.dim(res.data.generationId)}`);
|
|
26408
|
+
console.log();
|
|
26409
|
+
console.log(
|
|
26410
|
+
source_default.dim(`Track progress: ${source_default.bold(`lm deployments list --service ${res.data.serviceId}`)}`)
|
|
26411
|
+
);
|
|
26412
|
+
console.log(source_default.dim(`Stream logs: ${source_default.bold(`lm logs --service ${res.data.serviceId}`)}`));
|
|
26413
|
+
} catch (err) {
|
|
26414
|
+
spin.fail(`Template deploy failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
26415
|
+
process.exitCode = 1;
|
|
26416
|
+
}
|
|
26417
|
+
}
|
|
26418
|
+
function registerTemplate(program3) {
|
|
26419
|
+
const cmd = program3.command("template").alias("tpl").description("Curated starter templates \u2014 Railway-style one-shot deploy");
|
|
26420
|
+
cmd.command("list").alias("ls").description("List available templates").option("-c, --category <cat>", "Filter by category (fullstack, backend, frontend, ai, data, devtools)").action(listCmd);
|
|
26421
|
+
cmd.command("deploy <slug>").description("Deploy a template into a project").option("-p, --project <projectId>", "Project to deploy into (default: from .launchmatic.json or first project)").option("-n, --name <name>", "Service name override (default: template name)").option("-e, --env <KEY=value...>", "Set required env vars inline").option("-y, --yes", "Don't prompt \u2014 fail if required env vars are missing").action(deployCmd);
|
|
26422
|
+
}
|
|
26423
|
+
|
|
25182
26424
|
// src/index.ts
|
|
25183
26425
|
var program2 = new Command();
|
|
25184
|
-
program2.name("lm").description("Launchmatic CLI \u2014 deploy from your terminal").version("0.
|
|
26426
|
+
program2.name("lm").description("Launchmatic CLI \u2014 deploy from your terminal").version("0.5.1");
|
|
25185
26427
|
registerLogin(program2);
|
|
25186
26428
|
registerInit(program2);
|
|
25187
26429
|
registerDeploy(program2);
|
|
@@ -25201,4 +26443,10 @@ registerUsage(program2);
|
|
|
25201
26443
|
registerPreview(program2);
|
|
25202
26444
|
registerApiKey(program2);
|
|
25203
26445
|
registerDeployments(program2);
|
|
26446
|
+
registerAgent(program2);
|
|
26447
|
+
registerWorkflows(program2);
|
|
26448
|
+
registerImage(program2);
|
|
26449
|
+
registerMonorepo(program2);
|
|
26450
|
+
registerUp(program2);
|
|
26451
|
+
registerTemplate(program2);
|
|
25204
26452
|
program2.parse();
|