@xlaunch/cli 0.2.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +63 -0
- package/lib/bin.js +157 -0
- package/lib/dump-config-CMEroLOU.js +52 -0
- package/lib/plugin-CHlQ3vHX.js +130 -0
- package/lib/profile-boot-BSCjR7FQ.js +308 -0
- package/lib/profile-boot-CaxJ0Q_q.js +2 -0
- package/package.json +147 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Portions Copyright (c) 2026 Northlatch Labs LLC
|
|
4
|
+
Copyright (c) 2026 DeepSeek
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# `@xlaunch/cli`
|
|
2
|
+
|
|
3
|
+
Xlaunch is an AI agent that runs on your own computer. Give it a job and it does the work: it reads and writes files, runs the shell, browses the web, hands parts of the job to other agents, and keeps working on a schedule while you are away. This package is the `xlaunch` command.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install -g @xlaunch/cli
|
|
7
|
+
xlaunch web
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`xlaunch web` starts the Web UI at `http://127.0.0.1:3080` and opens it in your browser. On first run it asks for your Xlaunch Gateway address and key. Xlaunch needs Node.js 22.19 or newer. It is a product of Northlatch Labs LLC, released under the MIT licence.
|
|
11
|
+
|
|
12
|
+
## For developers
|
|
13
|
+
|
|
14
|
+
The `xlaunch` command is the sole supported Node application launcher: profiles are ordered stacks of plugin-bundle patch layers under the user's own overrides. SDK and ACP are profiles, not separate public bins. The Python runtime wheel packages this same command; the SDK defaults to `sdk`, and the minimal example selects `sdk-minimal`. [`src/args.ts`](src/args.ts) owns the command grammar, and [`src/bin.ts`](src/bin.ts) loads only the selected runner. Invalid commands, options from another mode, configuration errors, and boot failures exit nonzero.
|
|
15
|
+
|
|
16
|
+
## Entry modes
|
|
17
|
+
|
|
18
|
+
| Command | Purpose |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `xlaunch --profile <name>` | Boot the named profile under `$XLAUNCH_HOME/profiles/<name>`. |
|
|
21
|
+
| `xlaunch --profile acp` | Serve automation clients over ACP stdio until disconnect. |
|
|
22
|
+
| `xlaunch --profile sdk` | Serve SDK clients over JSON-RPC stdio until shutdown or disconnect. |
|
|
23
|
+
| `xlaunch --profile sdk-minimal` | Serve SDK clients with the standalone minimal agent tree. |
|
|
24
|
+
| `xlaunch web` | Alias of `--profile web`. |
|
|
25
|
+
| `xlaunch plugin --profile <name> <pnpm args>` | Manage a profile's plugins by forwarding to pnpm in the profile directory. |
|
|
26
|
+
|
|
27
|
+
The invoking directory is the default workspace root. The `web`, `sdk`, `sdk-minimal`, and `acp` profiles auto-initialize on first use from shipped templates; any other profile must be created through `xlaunch plugin`.
|
|
28
|
+
|
|
29
|
+
## App arguments
|
|
30
|
+
|
|
31
|
+
The launcher parses only its own flags and hands everything after them to the booted profile, where any injected app plugin may parse the shared immutable snapshot ([`xlaunch-cmdline`](../../packages/boot/cmdline/README.md)). The first token the launcher does not recognize starts the app's arguments:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
xlaunch --profile web --port 8080 # --port belongs to the web app
|
|
35
|
+
xlaunch --profile tui --resume <id> # example, assuming the tui profile is installed; --resume belongs to the terminal app
|
|
36
|
+
xlaunch --profile sdk # the SDK JSON-RPC server reads its own flags after the launcher flags
|
|
37
|
+
xlaunch --profile web --help # the web app's flags, not the launcher's
|
|
38
|
+
xlaunch --help # the launcher's own help
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
<a id="profiles"></a>
|
|
42
|
+
## Profiles
|
|
43
|
+
|
|
44
|
+
A profile directory holds a `package.json` (out-of-tree plugin dependencies plus the profile manifest `xlaunch.profile` with its ordered `bundles` list and `patchReload` lifecycle) and a `cordis.patch.yml` (the user's own patch layer). `patchReload: live` watches the profile and home-level patch files; `startup` applies them once.
|
|
45
|
+
|
|
46
|
+
The tree composes over an empty root:
|
|
47
|
+
- each bundle's patch in `xlaunch.profile.bundles` order
|
|
48
|
+
- then the profile's `cordis.patch.yml`, then the home-level `$XLAUNCH_HOME/cordis.patch.yml`
|
|
49
|
+
- then `--patch` overlays
|
|
50
|
+
|
|
51
|
+
Bundles named in `xlaunch.profile.bundles` resolve from the xlaunch installation first (`@xlaunch/base`, `@xlaunch/web-app`, `@xlaunch/sdk-app`, `@xlaunch/sdk-minimal`, `@xlaunch/acp-app`), then from the profile's own `node_modules`, where pnpm installs out-of-tree plugins.
|
|
52
|
+
|
|
53
|
+
Use `--dump-default-config` and `--dump-config` to inspect the composed tree without booting it.
|
|
54
|
+
|
|
55
|
+
The [CLI behavior reference](reference/README.md) owns exact layer precedence, flags, shutdown behavior, deployment defaults, and source execution.
|
|
56
|
+
|
|
57
|
+
## Optional overlays
|
|
58
|
+
|
|
59
|
+
`config/examples/` ships opt-in overlays for GitHub review webhooks, session-local Schedule, memory MCP servers, and runtime Cordis tools. They are never part of a default profile; the [user guides](../../docs/user/guide/index.md) and [developer practice guides](../../docs/user/develop/practice/index.md) own setup and safety instructions.
|
|
60
|
+
|
|
61
|
+
## Development
|
|
62
|
+
|
|
63
|
+
Production runs require built package and frontend artifacts. From the repository root, run `pnpm run build` separately, then use `pnpm xlaunch <args...>` to run the TypeScript entry and forward every argument; the [source-execution reference](reference/README.md#source-execution) owns the module-resolution contract.
|
package/lib/bin.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { loadLayeredEnv } from "@xlaunch/app-boot";
|
|
5
|
+
import { Command, CommanderError } from "commander";
|
|
6
|
+
//#region lib/types/args.js
|
|
7
|
+
/**
|
|
8
|
+
* Commander adapter for the `xlaunch` command line.
|
|
9
|
+
*
|
|
10
|
+
* The launcher parses only what it owns — which profile to boot, which extra
|
|
11
|
+
* patch overlays to apply, and the config dumps — and hands **everything after
|
|
12
|
+
* its own flags** to the booted tree verbatim, where injected app plugins parse
|
|
13
|
+
* their own flag families and print their own `--help` (see
|
|
14
|
+
* `@xlaunch/cmdline`). Launcher flags therefore come first: the first
|
|
15
|
+
* token this parser does not recognize starts the inner arguments, so
|
|
16
|
+
* `xlaunch --profile tui --resume abc` boots the tui profile with `--resume abc`,
|
|
17
|
+
* and `xlaunch --profile web -h` prints the web app's help, not this one's.
|
|
18
|
+
*
|
|
19
|
+
* `web` is a hardcoded alias for `--profile web`; `plugin` manages a profile's
|
|
20
|
+
* plugin dependencies by forwarding to pnpm.
|
|
21
|
+
* @module @xlaunch/cli/args
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Repeatable single-value collector: `--patch a.yml --patch b.yml`. Never
|
|
25
|
+
* variadic — a variadic `--patch` would swallow the inner arguments.
|
|
26
|
+
*/
|
|
27
|
+
const collect = (value, previous = []) => [...previous, value];
|
|
28
|
+
/** The launcher's own help text; each app prints its own. */
|
|
29
|
+
const HELP_EXAMPLES = `
|
|
30
|
+
Examples:
|
|
31
|
+
xlaunch --profile web boot the web profile (same as: xlaunch web)
|
|
32
|
+
xlaunch --profile tui --patch ./extra.yml boot a custom profile with one extra overlay
|
|
33
|
+
xlaunch --profile tui --resume <session> arguments after the launcher flags reach the app
|
|
34
|
+
xlaunch --profile web --help the web app's own flags and help
|
|
35
|
+
xlaunch plugin --profile tui add <package> install a plugin into the tui profile
|
|
36
|
+
`;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a boot or dump invocation from the launcher flags and the leftover
|
|
39
|
+
* inner arguments.
|
|
40
|
+
* @param program - the command whose options were parsed (the root, or the `web` alias).
|
|
41
|
+
* @param profile - the profile these flags boot.
|
|
42
|
+
* @param options - the launcher flags commander collected.
|
|
43
|
+
* @param args - the leftover arguments, in argv order.
|
|
44
|
+
* @returns the resolved invocation.
|
|
45
|
+
*/
|
|
46
|
+
function resolveBoot(program, profile, options, args) {
|
|
47
|
+
const patches = options.patch ?? [];
|
|
48
|
+
if (patches.includes("")) program.error("error: --patch needs a path");
|
|
49
|
+
if (options.dumpConfig !== true && options.dumpDefaultConfig !== true) return {
|
|
50
|
+
mode: "profile",
|
|
51
|
+
profile,
|
|
52
|
+
patches,
|
|
53
|
+
args
|
|
54
|
+
};
|
|
55
|
+
if (options.dumpConfig === true && options.dumpDefaultConfig === true) program.error("error: --dump-config and --dump-default-config are mutually exclusive");
|
|
56
|
+
if (args.length > 0) program.error(`error: config dumps take no app arguments, got ${args.map((argument) => JSON.stringify(argument)).join(" ")}`);
|
|
57
|
+
const defaultOnly = options.dumpDefaultConfig === true;
|
|
58
|
+
if (defaultOnly && patches.length > 0) program.error("error: --dump-default-config prints the bundle layers and takes no --patch");
|
|
59
|
+
return {
|
|
60
|
+
mode: "dump-config",
|
|
61
|
+
profile,
|
|
62
|
+
defaultOnly,
|
|
63
|
+
patches
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Resolve argv into one invocation, or print and exit for help, version, or an
|
|
68
|
+
* error.
|
|
69
|
+
* @param argv - arguments after the Node binary and script.
|
|
70
|
+
* @param version - version string printed by `--version`.
|
|
71
|
+
* @returns the resolved invocation.
|
|
72
|
+
*/
|
|
73
|
+
function parseXlaunchArgs(argv, version) {
|
|
74
|
+
let resolved;
|
|
75
|
+
const program = new Command();
|
|
76
|
+
program.name("xlaunch").version(version, "-V, --version", "output the version number").description("xlaunch: boot a Xlaunch profile — an ordered stack of plugin-bundle patch layers under your own overrides.").addHelpText("after", HELP_EXAMPLES).exitOverride().helpOption(false).allowUnknownOption().passThroughOptions().enablePositionalOptions().argument("[args...]", "arguments for the booted profile's app (see: xlaunch --profile <name> --help)").option("--profile <name>", "the profile under $XLAUNCH_HOME/profiles to boot").option("--patch <path>", "extra patch-list overlay applied after the profile layer (repeatable)", collect).option("--dump-config", "print the composed profile tree and exit").option("--dump-default-config", "print the profile tree without its user layer or --patch overlays and exit").action((args, options) => {
|
|
77
|
+
if (options.profile === void 0) {
|
|
78
|
+
if (args.some((argument) => argument === "-h" || argument === "--help")) program.help();
|
|
79
|
+
program.error("error: --profile <name> is required");
|
|
80
|
+
}
|
|
81
|
+
const profile = options.profile;
|
|
82
|
+
if (profile === "") program.error("error: --profile needs a name");
|
|
83
|
+
resolved = resolveBoot(program, profile, options, args);
|
|
84
|
+
});
|
|
85
|
+
/** Reject parent options supplied before a subcommand. */
|
|
86
|
+
const rejectParentOptions = (command) => {
|
|
87
|
+
const parent = program.opts();
|
|
88
|
+
if (parent.profile !== void 0 || parent.patch !== void 0 || parent.dumpConfig !== void 0 || parent.dumpDefaultConfig !== void 0) program.error(`error: ${command} takes none of parent --profile, --patch, --dump-config, or --dump-default-config`);
|
|
89
|
+
};
|
|
90
|
+
const web = program.command("web").description("boot the web profile (alias of --profile web); the web app's own flags follow");
|
|
91
|
+
web.helpOption(false).allowUnknownOption().passThroughOptions().enablePositionalOptions().argument("[args...]", "arguments for the web app (see: xlaunch web --help)").option("--patch <path>", "extra patch-list overlay applied after the profile layer (repeatable)", collect).option("--dump-config", "print the composed web-profile tree (with the user layer and any --patch) and exit").option("--dump-default-config", "print the web profile's bundle layers (no user layer) and exit").action((args, options) => {
|
|
92
|
+
rejectParentOptions("web");
|
|
93
|
+
resolved = resolveBoot(web, "web", options, args);
|
|
94
|
+
});
|
|
95
|
+
program.command("plugin").description("manage a profile's plugins by forwarding the remaining arguments to pnpm in the profile directory").requiredOption("--profile <name>", "the profile whose plugins to manage (initialized on first use)").allowUnknownOption().argument("[args...]", "pnpm arguments, forwarded verbatim (add <pkg>, remove <pkg>, why <pkg>, ...)").action((args, options) => {
|
|
96
|
+
rejectParentOptions("plugin");
|
|
97
|
+
if (options.profile === "") program.error("error: --profile needs a name");
|
|
98
|
+
if (args.length === 0) program.error("error: plugin needs pnpm arguments to forward (e.g. add <package>)");
|
|
99
|
+
resolved = {
|
|
100
|
+
mode: "plugin",
|
|
101
|
+
profile: options.profile,
|
|
102
|
+
args
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
try {
|
|
106
|
+
program.parse(argv, { from: "user" });
|
|
107
|
+
} catch (error) {
|
|
108
|
+
return process.exit(error instanceof CommanderError ? error.exitCode : 1);
|
|
109
|
+
}
|
|
110
|
+
/* v8 ignore next -- an action resolves or Commander throws */
|
|
111
|
+
if (resolved === void 0) throw new Error("xlaunch: no invocation resolved");
|
|
112
|
+
return resolved;
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region lib/types/bin.js
|
|
116
|
+
/**
|
|
117
|
+
* Command-line entry for xlaunch.
|
|
118
|
+
* @module @xlaunch/cli/bin
|
|
119
|
+
*/
|
|
120
|
+
/* v8 ignore file -- built-bin acceptance exercises this self-executing dispatch. */
|
|
121
|
+
function readVersion() {
|
|
122
|
+
const manifest = JSON.parse(readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"));
|
|
123
|
+
return typeof manifest.version === "string" ? manifest.version : "0.0.0";
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Run the public xlaunch command-line interface.
|
|
127
|
+
* @returns a promise that settles when the selected command mode finishes.
|
|
128
|
+
*/
|
|
129
|
+
async function runCli() {
|
|
130
|
+
const invocation = parseXlaunchArgs(process.argv.slice(2), readVersion());
|
|
131
|
+
switch (invocation.mode) {
|
|
132
|
+
case "profile": {
|
|
133
|
+
const { runProfile } = await import("./profile-boot-CaxJ0Q_q.js");
|
|
134
|
+
await runProfile({
|
|
135
|
+
environment: loadLayeredEnv("xlaunch"),
|
|
136
|
+
profile: invocation.profile,
|
|
137
|
+
patchFiles: invocation.patches,
|
|
138
|
+
args: invocation.args
|
|
139
|
+
});
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case "plugin": {
|
|
143
|
+
const { runPlugin } = await import("./plugin-CHlQ3vHX.js");
|
|
144
|
+
process.exit(runPlugin(invocation.profile, invocation.args));
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
case "dump-config": {
|
|
148
|
+
const { runDumpConfig } = await import("./dump-config-CMEroLOU.js");
|
|
149
|
+
runDumpConfig(invocation.profile, invocation.defaultOnly, invocation.patches);
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
default: throw new Error(`xlaunch: unhandled invocation mode ${JSON.stringify(invocation)}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (import.meta.main) await runCli();
|
|
156
|
+
//#endregion
|
|
157
|
+
export { runCli };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { i as prepareProfile, n as PROFILE_ROOT_FILENAME, r as homePatchPath } from "./profile-boot-BSCjR7FQ.js";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { loadOptionalPatches, loadOverlayPatches, renderConfigDump } from "@xlaunch/app-boot";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
//#region lib/types/dump-config.js
|
|
6
|
+
/**
|
|
7
|
+
* Config-dump entry for `xlaunch --profile <name> --dump-config`: compose the
|
|
8
|
+
* profile's patch layers through the include plugin's patch algorithm without
|
|
9
|
+
* booting or evaluating `!!js`, with one source layer per bundle, the
|
|
10
|
+
* profile's own patch file, and each `--patch` overlay.
|
|
11
|
+
* @module @xlaunch/cli/dump-config
|
|
12
|
+
*/
|
|
13
|
+
const NAME = "xlaunch";
|
|
14
|
+
/* v8 ignore start -- built-bin acceptance drives this boot-free dispatch */
|
|
15
|
+
/**
|
|
16
|
+
* Print a profile composition with comments naming each source file and patch layer.
|
|
17
|
+
* @param profile - the profile name.
|
|
18
|
+
* @param defaultOnly - omit the profile's user layer and `--patch` overlays
|
|
19
|
+
* (the recovery diagnostic for a broken `cordis.patch.yml`, which is then
|
|
20
|
+
* never parsed).
|
|
21
|
+
* @param patches - `--patch` overlay paths, in argv order.
|
|
22
|
+
*/
|
|
23
|
+
function runDumpConfig(profile, defaultOnly, patches) {
|
|
24
|
+
const loaded = prepareProfile(profile, !defaultOnly);
|
|
25
|
+
const layers = loaded.layers.map((layer) => ({
|
|
26
|
+
label: layer.packageName,
|
|
27
|
+
patches: layer.patches
|
|
28
|
+
}));
|
|
29
|
+
if (!defaultOnly) {
|
|
30
|
+
if (existsSync(loaded.patchPath)) layers.push({
|
|
31
|
+
label: loaded.patchPath,
|
|
32
|
+
patches: loaded.patches
|
|
33
|
+
});
|
|
34
|
+
const homePatchFile = homePatchPath();
|
|
35
|
+
const homePatches = loadOptionalPatches(NAME, homePatchFile);
|
|
36
|
+
if (homePatches !== void 0) layers.push({
|
|
37
|
+
label: homePatchFile,
|
|
38
|
+
patches: homePatches
|
|
39
|
+
});
|
|
40
|
+
for (const file of patches) {
|
|
41
|
+
const absolute = resolve(file);
|
|
42
|
+
layers.push({
|
|
43
|
+
label: absolute,
|
|
44
|
+
patches: loadOverlayPatches(NAME, absolute)
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
process.stdout.write(renderConfigDump(NAME, join(loaded.dir, PROFILE_ROOT_FILENAME), layers));
|
|
49
|
+
}
|
|
50
|
+
/* v8 ignore stop */
|
|
51
|
+
//#endregion
|
|
52
|
+
export { runDumpConfig };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { t as INSTALL_ANCHOR } from "./profile-boot-BSCjR7FQ.js";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { DEFAULT_PROFILE_BUNDLES, PROFILE_TEMPLATES, initProfile, readProfileManifest, resolveBundleDir, resolveProfileDir, writeProfileManifest } from "@xlaunch/app-boot";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
//#region lib/types/plugin.js
|
|
7
|
+
/**
|
|
8
|
+
* `xlaunch plugin --profile <name> <args...>` — profile plugin management as a
|
|
9
|
+
* thin pnpm forwarder: initialize the profile on first use, run
|
|
10
|
+
* `pnpm <args...>` in the profile directory, then reconcile the
|
|
11
|
+
* `xlaunch.profile.bundles` layer list against the installed state (a dependency
|
|
12
|
+
* resolving to a package that declares `xlaunch.bundle` joins the layer stack; a
|
|
13
|
+
* removed or bundle-less dependency leaves it). Reconciling by installed
|
|
14
|
+
* state, not by dependency diff, means `update` activates a package that
|
|
15
|
+
* gained its `xlaunch.bundle` declaration in a newer version.
|
|
16
|
+
* @module @xlaunch/cli/plugin
|
|
17
|
+
*/
|
|
18
|
+
const NAME = "xlaunch";
|
|
19
|
+
/**
|
|
20
|
+
* Whether a resolved dependency exports a profile patch, i.e. is a bundle.
|
|
21
|
+
* @param packageName - the dependency's package name.
|
|
22
|
+
* @param profileDir - the profile directory (resolution anchor).
|
|
23
|
+
* @returns true when the package manifest declares `xlaunch.bundle`.
|
|
24
|
+
*/
|
|
25
|
+
function exportsPatch(packageName, profileDir) {
|
|
26
|
+
let dir;
|
|
27
|
+
try {
|
|
28
|
+
dir = resolveBundleDir(NAME, packageName, INSTALL_ANCHOR, profileDir);
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return readProfileManifest(NAME, dir).xlaunch?.bundle?.patch !== void 0;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reconcile `xlaunch.profile.bundles` against the installed state: pnpm has
|
|
36
|
+
* already written the real installed names (so a git/path/tarball/alias spec
|
|
37
|
+
* on the command line reconciles by its true package name) and materialized
|
|
38
|
+
* the packages. A dependency that resolves to a `xlaunch.bundle`-declaring
|
|
39
|
+
* package joins the layer stack (appended in dependency order); a
|
|
40
|
+
* dependency-listed name that no longer does — removed, or the installed
|
|
41
|
+
* version dropped the declaration — leaves it. In-box bundles from the
|
|
42
|
+
* profile template are not dependencies and are never touched. Warns once
|
|
43
|
+
* per newly-added bundle-less dependency (a plain library is fine; the
|
|
44
|
+
* warning is orientation).
|
|
45
|
+
*/
|
|
46
|
+
function reconcilePlugins(before, profileDir) {
|
|
47
|
+
const after = readProfileManifest(NAME, profileDir);
|
|
48
|
+
const beforeDeps = new Set(Object.keys(before.dependencies ?? {}));
|
|
49
|
+
const dependencies = Object.keys(after.dependencies ?? {});
|
|
50
|
+
const plugins = after.xlaunch?.profile?.bundles ?? [];
|
|
51
|
+
let changed = false;
|
|
52
|
+
for (const packageName of dependencies) {
|
|
53
|
+
const isBundle = exportsPatch(packageName, profileDir);
|
|
54
|
+
if (isBundle && !plugins.includes(packageName)) {
|
|
55
|
+
plugins.push(packageName);
|
|
56
|
+
changed = true;
|
|
57
|
+
} else if (!isBundle && !beforeDeps.has(packageName)) process.stderr.write(`${NAME}: warning: ${packageName} declares no xlaunch.bundle — installed as a plain dependency, not a profile layer (a later update that gains one activates it automatically)
|
|
58
|
+
`);
|
|
59
|
+
}
|
|
60
|
+
const dependencySet = new Set(dependencies);
|
|
61
|
+
for (const packageName of [...plugins]) {
|
|
62
|
+
const wasDependency = beforeDeps.has(packageName) || dependencySet.has(packageName);
|
|
63
|
+
const stillBundle = dependencySet.has(packageName) && exportsPatch(packageName, profileDir);
|
|
64
|
+
if (wasDependency && !stillBundle) {
|
|
65
|
+
plugins.splice(plugins.indexOf(packageName), 1);
|
|
66
|
+
changed = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!changed) return;
|
|
70
|
+
after.xlaunch = {
|
|
71
|
+
...after.xlaunch,
|
|
72
|
+
profile: {
|
|
73
|
+
...after.xlaunch?.profile,
|
|
74
|
+
bundles: plugins
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
writeProfileManifest(profileDir, after);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Rewrite relative filesystem specs against the user's invoking directory.
|
|
81
|
+
* pnpm runs with cwd = the profile directory, so a bare `.` or `../plugin`
|
|
82
|
+
* (or their `file:`/`link:` forms) would silently resolve inside the profile
|
|
83
|
+
* — `add .` from a plugin checkout would self-link the profile. Absolute
|
|
84
|
+
* specs, registry names, and every other pnpm argument pass through
|
|
85
|
+
* untouched.
|
|
86
|
+
* @param argument - one pnpm argument, verbatim from argv.
|
|
87
|
+
* @param cwd - the directory `xlaunch` was invoked from.
|
|
88
|
+
* @returns the argument with a relative path spec anchored to `cwd`.
|
|
89
|
+
*/
|
|
90
|
+
function anchorPathSpec(argument, cwd) {
|
|
91
|
+
const match = /^(?<prefix>(?:file|link):)?(?<path>\.{1,2}(?:[/\\].*)?)$/.exec(argument);
|
|
92
|
+
if (match?.groups?.path === void 0) return argument;
|
|
93
|
+
return `${match.groups.prefix ?? ""}${resolve(cwd, match.groups.path)}`;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Run one `xlaunch plugin` invocation: init if needed, forward to pnpm, reconcile.
|
|
97
|
+
* @param profile - the profile name.
|
|
98
|
+
* @param args - pnpm arguments with relative path specs anchored to the invoking directory.
|
|
99
|
+
* @returns the pnpm exit code.
|
|
100
|
+
*/
|
|
101
|
+
function runPlugin(profile, args) {
|
|
102
|
+
const dir = resolveProfileDir(profile);
|
|
103
|
+
if (!existsSync(join(dir, "package.json"))) {
|
|
104
|
+
const template = PROFILE_TEMPLATES[profile];
|
|
105
|
+
initProfile(dir, template?.bundles ?? DEFAULT_PROFILE_BUNDLES, template?.patchReload);
|
|
106
|
+
process.stderr.write(`${NAME}: initialized profile ${profile} at ${dir}\n`);
|
|
107
|
+
}
|
|
108
|
+
const before = readProfileManifest(NAME, dir);
|
|
109
|
+
const result = spawnSync("pnpm", args.map((argument) => anchorPathSpec(argument, process.cwd())), {
|
|
110
|
+
cwd: dir,
|
|
111
|
+
stdio: "inherit",
|
|
112
|
+
shell: process.platform === "win32"
|
|
113
|
+
});
|
|
114
|
+
if (result.error !== void 0) {
|
|
115
|
+
if (result.error.code === "ENOENT") {
|
|
116
|
+
process.stderr.write(`${NAME}: pnpm not found on PATH — install pnpm to manage profile plugins\n`);
|
|
117
|
+
return 127;
|
|
118
|
+
}
|
|
119
|
+
throw result.error;
|
|
120
|
+
}
|
|
121
|
+
const exitCode = result.status ?? 1;
|
|
122
|
+
if (exitCode === 0) reconcilePlugins(before, dir);
|
|
123
|
+
else {
|
|
124
|
+
process.stderr.write(`${NAME}: pnpm failed in profile directory ${dir}\n`);
|
|
125
|
+
if (args.some((argument) => /^git\+|^github:|\.git(?:#|$)/.test(argument))) process.stderr.write(`${NAME}: git-hosted plugins build on install via their prepare script, which pnpm blocks until allowed — add the exact key pnpm printed above under allowBuilds in ${join(dir, "pnpm-workspace.yaml")}, then re-run\n`);
|
|
126
|
+
}
|
|
127
|
+
return exitCode;
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { runPlugin };
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { PROFILE_PATCH_FILENAME, boot, composeEntries, healProfilesModuleFallback, installFailLoud, loadOptionalPatches, loadOverlayPatches, loadProfile, watchUserPatches } from "@xlaunch/app-boot";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { resolveXlaunchHome } from "@xlaunch/home-paths";
|
|
6
|
+
import { installProxyFromEnvironment } from "@xlaunch/http-proxy";
|
|
7
|
+
import { XLAUNCH_LAUNCH_ENVIRONMENT_KEY } from "@xlaunch/launch-environment";
|
|
8
|
+
import { provideCmdline } from "@xlaunch/cmdline";
|
|
9
|
+
//#region lib/types/process-shutdown.js
|
|
10
|
+
/** Bounded, escalating process shutdown for the long-lived CLI surfaces. */
|
|
11
|
+
/** Maximum grace allowed for the application tree to dispose before process exit. */
|
|
12
|
+
const PROCESS_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
13
|
+
/**
|
|
14
|
+
* Create one process-exit controller around an application disposer.
|
|
15
|
+
* @param dispose - Whole-application teardown that resolves at quiescence.
|
|
16
|
+
* @param forceExit - Function that exits the process immediately, replaceable by tests.
|
|
17
|
+
* @param complete - Function that records the natural completion code, replaceable by tests.
|
|
18
|
+
* @param timeoutMs - Grace before forced exit, replaceable by tests.
|
|
19
|
+
* @returns A controller whose normal calls coalesce and whose repeated signal call escalates.
|
|
20
|
+
*/
|
|
21
|
+
function createProcessShutdown(dispose, forceExit = (code) => {
|
|
22
|
+
process.exit(code);
|
|
23
|
+
}, complete = (code) => {
|
|
24
|
+
process.exitCode = code;
|
|
25
|
+
}, timeoutMs = PROCESS_SHUTDOWN_TIMEOUT_MS) {
|
|
26
|
+
let pending;
|
|
27
|
+
let timeout;
|
|
28
|
+
let completed = false;
|
|
29
|
+
let forceExited = false;
|
|
30
|
+
const clearExitTimeout = () => {
|
|
31
|
+
/* v8 ignore else -- shutdown() arms the timer before any asynchronous exit path can run. */
|
|
32
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
33
|
+
};
|
|
34
|
+
const forceExitOnce = (code) => {
|
|
35
|
+
if (forceExited) return;
|
|
36
|
+
forceExited = true;
|
|
37
|
+
clearExitTimeout();
|
|
38
|
+
forceExit(code);
|
|
39
|
+
};
|
|
40
|
+
const completeOnce = (code) => {
|
|
41
|
+
if (completed || forceExited) return;
|
|
42
|
+
completed = true;
|
|
43
|
+
clearExitTimeout();
|
|
44
|
+
complete(code);
|
|
45
|
+
};
|
|
46
|
+
const start = (code, forceAfterDispose) => {
|
|
47
|
+
if (pending !== void 0) return pending;
|
|
48
|
+
timeout = setTimeout(() => {
|
|
49
|
+
forceExitOnce(code);
|
|
50
|
+
}, timeoutMs);
|
|
51
|
+
pending = Promise.resolve().then(dispose).then(() => {
|
|
52
|
+
if (forceAfterDispose) forceExitOnce(code);
|
|
53
|
+
else completeOnce(code);
|
|
54
|
+
}, () => {
|
|
55
|
+
forceExitOnce(code);
|
|
56
|
+
});
|
|
57
|
+
return pending;
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
shutdown(code) {
|
|
61
|
+
return start(code, false);
|
|
62
|
+
},
|
|
63
|
+
interrupt(code) {
|
|
64
|
+
if (pending !== void 0) {
|
|
65
|
+
forceExitOnce(code);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
start(code, true);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region lib/types/profile-boot.js
|
|
74
|
+
/**
|
|
75
|
+
* Shared profile boot for every `xlaunch` surface: resolve the profile, stack its
|
|
76
|
+
* patch layers (bundle layers in `xlaunch.profile.bundles` order, the profile's
|
|
77
|
+
* own `cordis.patch.yml`, `--patch` overlays, the telemetry switch), mount the
|
|
78
|
+
* tree over the profile's empty root config, apply its selected patch-reload
|
|
79
|
+
* lifecycle, and wire fail-loud plus bounded shutdown.
|
|
80
|
+
*
|
|
81
|
+
* App flags are not the launcher's business: the invocation's inner arguments
|
|
82
|
+
* are provided to the tree through `ctx.cmdlineArgs`, where any injected app
|
|
83
|
+
* plugin may read the same immutable snapshot.
|
|
84
|
+
* @module @xlaunch/cli/profile-boot
|
|
85
|
+
*/
|
|
86
|
+
const NAME = "xlaunch";
|
|
87
|
+
/** Launcher-owned readiness signal committed only after boot and host setup succeed. */
|
|
88
|
+
function createAppReady() {
|
|
89
|
+
let ready = false;
|
|
90
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
91
|
+
return {
|
|
92
|
+
service: { onReady(listener) {
|
|
93
|
+
if (ready) {
|
|
94
|
+
listener();
|
|
95
|
+
return () => {};
|
|
96
|
+
}
|
|
97
|
+
listeners.add(listener);
|
|
98
|
+
return () => {
|
|
99
|
+
listeners.delete(listener);
|
|
100
|
+
};
|
|
101
|
+
} },
|
|
102
|
+
commit() {
|
|
103
|
+
if (ready) return;
|
|
104
|
+
ready = true;
|
|
105
|
+
for (const listener of [...listeners]) listener();
|
|
106
|
+
listeners.clear();
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The home-level user patch layer (`$XLAUNCH_HOME/cordis.patch.yml`), applied
|
|
112
|
+
* over every profile's own layer. Resolved per call, not at module load:
|
|
113
|
+
* `$XLAUNCH_HOME` may be set by the test or launcher after import.
|
|
114
|
+
* @returns the absolute patch-file path.
|
|
115
|
+
*/
|
|
116
|
+
function homePatchPath() {
|
|
117
|
+
return join(resolveXlaunchHome(), PROFILE_PATCH_FILENAME);
|
|
118
|
+
}
|
|
119
|
+
/** Absolute path of this xlaunch installation's package.json (both anchors: src/ and lib/ sit one level under apps/cli). */
|
|
120
|
+
const INSTALL_ANCHOR = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
121
|
+
/**
|
|
122
|
+
* The row id the XLAUNCH_TELEMETRY_DISABLED switch targets: the session-telemetry
|
|
123
|
+
* exporter row a deployment may mount over the base bundle. The shipped bundles
|
|
124
|
+
* mount no exporter, so the switch is a no-op unless a later layer inserts one.
|
|
125
|
+
*/
|
|
126
|
+
const TELEMETRY_ROW_ID = "session-telemetry";
|
|
127
|
+
/** The empty root entry list every profile tree patches over. */
|
|
128
|
+
const PROFILE_ROOT_CONFIG = `# xlaunch profile root — an empty entry list. The tree is composed as patches:
|
|
129
|
+
# each bundle in package.json's xlaunch.profile.bundles, then cordis.patch.yml, then any
|
|
130
|
+
# --patch overlays. Edit cordis.patch.yml, not this file.
|
|
131
|
+
[]
|
|
132
|
+
`;
|
|
133
|
+
/** Root config filename inside a profile directory. */
|
|
134
|
+
const PROFILE_ROOT_FILENAME = "cordis.yml";
|
|
135
|
+
/**
|
|
136
|
+
* Resolve the telemetry opt-out switch into its boot patch. ANY non-empty
|
|
137
|
+
* value (including `'0'`/`'false'`) disables: a privacy switch prefers
|
|
138
|
+
* off-by-mistake over on-by-mistake. A composition without the telemetry row
|
|
139
|
+
* exports nothing, so the switch is then trivially satisfied and no patch is
|
|
140
|
+
* generated — custom profiles need not mount telemetry to run with the
|
|
141
|
+
* switch set.
|
|
142
|
+
* @param disabledEnv - the raw `XLAUNCH_TELEMETRY_DISABLED` value (`undefined` when unset).
|
|
143
|
+
* @param hasRow - whether the composition carries the telemetry row.
|
|
144
|
+
* @returns the disable patch, or `undefined` when no hard-disable patch is required.
|
|
145
|
+
*/
|
|
146
|
+
function resolveTelemetryPatch(disabledEnv, hasRow) {
|
|
147
|
+
if ((disabledEnv ?? "") === "" || !hasRow) return void 0;
|
|
148
|
+
return {
|
|
149
|
+
id: TELEMETRY_ROW_ID,
|
|
150
|
+
disabled: true
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Load a resolved profile for `name` and (re)write the empty root config. The
|
|
155
|
+
* root is always rewritten: the whole composition is patch layers, and the
|
|
156
|
+
* vendored Loader's tree write-back (a plugin self-disposing persists the
|
|
157
|
+
* current tree) can bake composed rows into this file — which would duplicate
|
|
158
|
+
* every bundle insert on the next boot. The file exists on disk only because
|
|
159
|
+
* the Loader needs a real include root to anchor `baseUrl` at the profile
|
|
160
|
+
* directory (the config dump anchors on the same file, so both compose over
|
|
161
|
+
* the identical base).
|
|
162
|
+
* @param name - the profile name.
|
|
163
|
+
* @param userLayer - `false` skips parsing `cordis.patch.yml` (the default dump).
|
|
164
|
+
* @returns the loaded profile.
|
|
165
|
+
*/
|
|
166
|
+
function prepareProfile(name, userLayer = true) {
|
|
167
|
+
const profile = loadProfile(NAME, name, INSTALL_ANCHOR, void 0, { userLayer });
|
|
168
|
+
writeFileSync(join(profile.dir, PROFILE_ROOT_FILENAME), PROFILE_ROOT_CONFIG);
|
|
169
|
+
return profile;
|
|
170
|
+
}
|
|
171
|
+
/** The full patch stack of one composed profile, in application order. */
|
|
172
|
+
function allPatches(composed) {
|
|
173
|
+
return [
|
|
174
|
+
...composed.bundlePatches,
|
|
175
|
+
...composed.profile.patches,
|
|
176
|
+
...composed.homePatches,
|
|
177
|
+
...composed.overlays
|
|
178
|
+
];
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Load `name` and compose its effective patch stack: bundle layers in
|
|
182
|
+
* `xlaunch.profile.bundles` order (a base-backed profile gets the base bundle's
|
|
183
|
+
* platform-gated shell rows), the profile's user layer, the home-level user
|
|
184
|
+
* layer (`$XLAUNCH_HOME/cordis.patch.yml` — machine-local preferences that apply
|
|
185
|
+
* to every profile, so it outranks the per-profile layer), `--patch` overlays,
|
|
186
|
+
* then the telemetry switch.
|
|
187
|
+
* @param name - the profile name.
|
|
188
|
+
* @param patchFiles - `--patch` overlay paths, in argv order.
|
|
189
|
+
* @returns the profile and its patch layers.
|
|
190
|
+
*/
|
|
191
|
+
async function composeProfile(name, patchFiles) {
|
|
192
|
+
const profile = prepareProfile(name);
|
|
193
|
+
await healProfilesModuleFallback({
|
|
194
|
+
installAnchor: INSTALL_ANCHOR,
|
|
195
|
+
profile
|
|
196
|
+
});
|
|
197
|
+
const homePatches = loadOptionalPatches(NAME, homePatchPath()) ?? [];
|
|
198
|
+
const overlays = patchFiles.flatMap((file) => loadOverlayPatches(NAME, resolve(file)));
|
|
199
|
+
const bundlePatches = profile.layers.flatMap((layer) => layer.patches);
|
|
200
|
+
const rows = /* @__PURE__ */ new Map();
|
|
201
|
+
for (const row of composeEntries([
|
|
202
|
+
bundlePatches,
|
|
203
|
+
profile.patches,
|
|
204
|
+
homePatches,
|
|
205
|
+
overlays
|
|
206
|
+
])) if (typeof row.id === "string") rows.set(row.id, row);
|
|
207
|
+
const composedOverlays = [...overlays];
|
|
208
|
+
const telemetryPatch = resolveTelemetryPatch(process.env.XLAUNCH_TELEMETRY_DISABLED, rows.has(TELEMETRY_ROW_ID));
|
|
209
|
+
if (telemetryPatch !== void 0) composedOverlays.push(telemetryPatch);
|
|
210
|
+
return {
|
|
211
|
+
profile,
|
|
212
|
+
bundlePatches,
|
|
213
|
+
homePatches,
|
|
214
|
+
overlays: composedOverlays
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Re-throw a watcher-setup failure unless a shutdown already owns the tree:
|
|
219
|
+
* a signal aborted this invocation, or an app requested exit (`ctx.appExit`
|
|
220
|
+
* from a fast one-shot) and the root's disposal rejected the in-flight setup
|
|
221
|
+
* await. Either way the failure describes a tree that is exiting as asked,
|
|
222
|
+
* not a broken watch.
|
|
223
|
+
* @param ctx - the booted root context.
|
|
224
|
+
* @param signal - this invocation's signal-shutdown fact.
|
|
225
|
+
* @param error - the setup failure.
|
|
226
|
+
*/
|
|
227
|
+
function suppressShutdownError(ctx, signal, error) {
|
|
228
|
+
if (signal.aborted) return;
|
|
229
|
+
if (ctx.fiber.state !== 2 || ctx.get("loader") === void 0) return;
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Boot one profile invocation end to end and leave process lifetime to the
|
|
234
|
+
* mounted plugins (or to a one-shot runner the composition mounts).
|
|
235
|
+
* @param options - environment snapshot, profile name, overlays, and the booted app's own arguments.
|
|
236
|
+
* @returns the settled root context and the shutdown controller.
|
|
237
|
+
*/
|
|
238
|
+
async function runProfile(options) {
|
|
239
|
+
const disposeProxy = await installProxyFromEnvironment(options.environment, (message) => {
|
|
240
|
+
process.stderr.write(`${NAME}: ${message}\n`);
|
|
241
|
+
});
|
|
242
|
+
const composed = await composeProfile(options.profile, options.patchFiles);
|
|
243
|
+
const app = {};
|
|
244
|
+
const appReady = createAppReady();
|
|
245
|
+
const shutdown = createProcessShutdown(async () => {
|
|
246
|
+
await app.current?.fiber.dispose();
|
|
247
|
+
await disposeProxy();
|
|
248
|
+
});
|
|
249
|
+
const signalShutdown = new AbortController();
|
|
250
|
+
const interrupt = (code) => {
|
|
251
|
+
signalShutdown.abort();
|
|
252
|
+
shutdown.interrupt(code);
|
|
253
|
+
};
|
|
254
|
+
process.on("SIGTERM", () => {
|
|
255
|
+
interrupt(0);
|
|
256
|
+
});
|
|
257
|
+
process.on("SIGINT", () => {
|
|
258
|
+
interrupt(130);
|
|
259
|
+
});
|
|
260
|
+
installFailLoud(NAME, process, async () => {
|
|
261
|
+
await app.current?.fiber.dispose();
|
|
262
|
+
});
|
|
263
|
+
const rootConfig = join(composed.profile.dir, PROFILE_ROOT_FILENAME);
|
|
264
|
+
const composeLive = () => structuredClone([
|
|
265
|
+
...composed.bundlePatches,
|
|
266
|
+
...loadOptionalPatches(NAME, composed.profile.patchPath) ?? [],
|
|
267
|
+
...loadOptionalPatches(NAME, homePatchPath()) ?? [],
|
|
268
|
+
...composed.overlays
|
|
269
|
+
]);
|
|
270
|
+
const ctx = await boot(NAME, rootConfig, structuredClone(allPatches(composed)), (hostCtx) => {
|
|
271
|
+
app.current = hostCtx;
|
|
272
|
+
hostCtx.provide(XLAUNCH_LAUNCH_ENVIRONMENT_KEY, options.environment);
|
|
273
|
+
provideCmdline(hostCtx, {
|
|
274
|
+
args: options.args,
|
|
275
|
+
exit: (code) => void shutdown.shutdown(code),
|
|
276
|
+
ready: appReady.service
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
app.current = ctx;
|
|
280
|
+
if (composed.profile.patchReload === "live" && !signalShutdown.signal.aborted && ctx.fiber.state === 2 && ctx.get("loader") !== void 0) try {
|
|
281
|
+
if (ctx.get("hmr") === void 0) {
|
|
282
|
+
if (ctx.get("timer") === void 0) await ctx.loader.create({ name: "@xlaunch/cordis-plugin-timer" });
|
|
283
|
+
await ctx.loader.create({
|
|
284
|
+
name: "@xlaunch/cordis-plugin-hmr",
|
|
285
|
+
config: { root: [] }
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
await watchUserPatches(ctx, {
|
|
289
|
+
binName: NAME,
|
|
290
|
+
filename: composed.profile.patchPath,
|
|
291
|
+
compose: composeLive
|
|
292
|
+
});
|
|
293
|
+
await watchUserPatches(ctx, {
|
|
294
|
+
binName: NAME,
|
|
295
|
+
filename: homePatchPath(),
|
|
296
|
+
compose: composeLive
|
|
297
|
+
});
|
|
298
|
+
} catch (error) {
|
|
299
|
+
suppressShutdownError(ctx, signalShutdown.signal, error);
|
|
300
|
+
}
|
|
301
|
+
if (!signalShutdown.signal.aborted && ctx.fiber.state === 2 && ctx.get("loader") !== void 0) appReady.commit();
|
|
302
|
+
return {
|
|
303
|
+
ctx,
|
|
304
|
+
shutdown
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
//#endregion
|
|
308
|
+
export { resolveTelemetryPatch as a, prepareProfile as i, PROFILE_ROOT_FILENAME as n, runProfile as o, homePatchPath as r, INSTALL_ANCHOR as t };
|
package/package.json
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xlaunch/cli",
|
|
3
|
+
"description": "xlaunch CLI: profile boot, plugin management, and the browser UI alias",
|
|
4
|
+
"version": "0.2.0-beta.1",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Northlatch-Labs-LLC/xlaunch-agent.git",
|
|
11
|
+
"directory": "apps/cli"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"bin": {
|
|
15
|
+
"xlaunch": "lib/bin.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"lib/*.js"
|
|
19
|
+
],
|
|
20
|
+
"xlaunch": {
|
|
21
|
+
"configTrees": [
|
|
22
|
+
{
|
|
23
|
+
"mount": "config/agent-presets",
|
|
24
|
+
"path": "../../packages/preset/agent-presets/presets",
|
|
25
|
+
"scanRoster": true
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"author": "Northlatch Labs LLC",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"commander": "^15.0.0",
|
|
33
|
+
"js-yaml": "^4.2.0",
|
|
34
|
+
"node-addon-require-builtin": "^0.1.4",
|
|
35
|
+
"@xlaunch/acp-app": "^0.2.0-beta.1",
|
|
36
|
+
"@xlaunch/agent-tool-presentation": "^0.2.0-beta.1",
|
|
37
|
+
"@xlaunch/api-files-controller": "^0.2.0-beta.1",
|
|
38
|
+
"@xlaunch/agent-instructions": "^0.2.0-beta.1",
|
|
39
|
+
"@xlaunch/app-boot": "^0.2.0-beta.1",
|
|
40
|
+
"@xlaunch/base": "^0.2.0-beta.1",
|
|
41
|
+
"@xlaunch/client-ui-agent-preset": "^0.2.0-beta.1",
|
|
42
|
+
"@xlaunch/client-ui-cordis": "^0.2.0-beta.1",
|
|
43
|
+
"@xlaunch/client-ui-workbench": "^0.2.0-beta.1",
|
|
44
|
+
"@xlaunch/client-ui-workbench-files": "^0.2.0-beta.1",
|
|
45
|
+
"@xlaunch/cmdline": "^0.2.0-beta.1",
|
|
46
|
+
"@xlaunch/command-compact": "^0.2.0-beta.1",
|
|
47
|
+
"@xlaunch/command-goal": "^0.2.0-beta.1",
|
|
48
|
+
"@xlaunch/compaction-basic": "^0.2.0-beta.1",
|
|
49
|
+
"@xlaunch/cordis": "^4.0.2",
|
|
50
|
+
"@xlaunch/compaction-tool-result-pruner": "^0.2.0-beta.1",
|
|
51
|
+
"@xlaunch/cordis-client-runner": "^0.2.0-beta.1",
|
|
52
|
+
"@xlaunch/cordis-plugin-hmr": "^1.0.17",
|
|
53
|
+
"@xlaunch/cordis-plugin-include": "^1.0.7",
|
|
54
|
+
"@xlaunch/cordis-plugin-loader": "^1.0.3",
|
|
55
|
+
"@xlaunch/cordis-plugin-timer": "^1.1.4",
|
|
56
|
+
"@xlaunch/fs-local": "^0.2.0-beta.1",
|
|
57
|
+
"@xlaunch/goal": "^0.2.0-beta.1",
|
|
58
|
+
"@xlaunch/goal-round-driver": "^0.2.0-beta.1",
|
|
59
|
+
"@xlaunch/home-paths": "^0.2.0-beta.1",
|
|
60
|
+
"@xlaunch/hooks-claude-code": "^0.2.0-beta.1",
|
|
61
|
+
"@xlaunch/http-proxy": "^0.2.0-beta.1",
|
|
62
|
+
"@xlaunch/jobs-local": "^0.2.0-beta.1",
|
|
63
|
+
"@xlaunch/launch-environment": "^0.2.0-beta.1",
|
|
64
|
+
"@xlaunch/hooks-codex": "^0.2.0-beta.1",
|
|
65
|
+
"@xlaunch/mcp-client": "^0.2.0-beta.1",
|
|
66
|
+
"@xlaunch/persona": "^0.2.0-beta.1",
|
|
67
|
+
"@xlaunch/plan-mode": "^0.2.0-beta.1",
|
|
68
|
+
"@xlaunch/pwsh-local": "^0.2.0-beta.1",
|
|
69
|
+
"@xlaunch/pwsh-sandbox": "^0.2.0-beta.1",
|
|
70
|
+
"@xlaunch/schedule": "^0.2.0-beta.1",
|
|
71
|
+
"@xlaunch/schemastery": "^3.18.2",
|
|
72
|
+
"@xlaunch/sdk-minimal": "^0.2.0-beta.1",
|
|
73
|
+
"@xlaunch/session-projection": "^0.2.0-beta.1",
|
|
74
|
+
"@xlaunch/sdk-app": "^0.2.0-beta.1",
|
|
75
|
+
"@xlaunch/session-reference": "^0.2.0-beta.1",
|
|
76
|
+
"@xlaunch/skill": "^0.2.0-beta.1",
|
|
77
|
+
"@xlaunch/terminal": "^0.2.0-beta.1",
|
|
78
|
+
"@xlaunch/skill-filesystem": "^0.2.0-beta.1",
|
|
79
|
+
"@xlaunch/time-context": "^0.2.0-beta.1",
|
|
80
|
+
"@xlaunch/terminal-bash": "^0.2.0-beta.1",
|
|
81
|
+
"@xlaunch/tmux-context": "^0.2.0-beta.1",
|
|
82
|
+
"@xlaunch/token-meter": "^0.2.0-beta.1",
|
|
83
|
+
"@xlaunch/tool-ask-user": "^0.2.0-beta.1",
|
|
84
|
+
"@xlaunch/tool-bash": "^0.2.0-beta.1",
|
|
85
|
+
"@xlaunch/tool-bash-persistent": "^0.2.0-beta.1",
|
|
86
|
+
"@xlaunch/tool-cordis": "^0.2.0-beta.1",
|
|
87
|
+
"@xlaunch/tool-fs": "^0.2.0-beta.1",
|
|
88
|
+
"@xlaunch/tool-goal": "^0.2.0-beta.1",
|
|
89
|
+
"@xlaunch/tool-fs-search": "^0.2.0-beta.1",
|
|
90
|
+
"@xlaunch/tool-pwsh": "^0.2.0-beta.1",
|
|
91
|
+
"@xlaunch/tool-jobs": "^0.2.0-beta.1",
|
|
92
|
+
"@xlaunch/tool-pwsh-persistent": "^0.2.0-beta.1",
|
|
93
|
+
"@xlaunch/tool-ralph": "^0.2.0-beta.1",
|
|
94
|
+
"@xlaunch/tool-skill": "^0.2.0-beta.1",
|
|
95
|
+
"@xlaunch/tool-str-replace-editor": "^0.2.0-beta.1",
|
|
96
|
+
"@xlaunch/tool-subagent-control": "^0.2.0-beta.1",
|
|
97
|
+
"@xlaunch/tool-web": "^0.2.0-beta.1",
|
|
98
|
+
"@xlaunch/tool-subagent": "^0.2.0-beta.1",
|
|
99
|
+
"@xlaunch/tool-todo": "^0.2.0-beta.1",
|
|
100
|
+
"@xlaunch/web-app": "^0.2.0-beta.1",
|
|
101
|
+
"@xlaunch/tool-workflow": "^0.2.0-beta.1",
|
|
102
|
+
"@xlaunch/webhook": "^0.2.0-beta.1",
|
|
103
|
+
"@xlaunch/webhook-github": "^0.2.0-beta.1",
|
|
104
|
+
"@xlaunch/workflow-worker-thread": "^0.2.0-beta.1",
|
|
105
|
+
"@xlaunch/api-capabilities-controller": "^0.2.0-beta.1",
|
|
106
|
+
"@xlaunch/hook-protocol": "^0.2.0-beta.1"
|
|
107
|
+
},
|
|
108
|
+
"devDependencies": {
|
|
109
|
+
"@agentclientprotocol/sdk": "1.4.0",
|
|
110
|
+
"@types/js-yaml": "^4.0.9",
|
|
111
|
+
"@types/ws": "8.18.1",
|
|
112
|
+
"execa": "^10.0.0",
|
|
113
|
+
"ws": "8.21.0",
|
|
114
|
+
"@xlaunch/acp": "^0.2.0-beta.1",
|
|
115
|
+
"@xlaunch/agent": "^0.2.0-beta.1",
|
|
116
|
+
"@xlaunch/agent-loop": "^0.2.0-beta.1",
|
|
117
|
+
"@xlaunch/attachment-local": "^0.2.0-beta.1",
|
|
118
|
+
"@xlaunch/agent-loop-testkit": "^0.2.0-beta.1",
|
|
119
|
+
"@xlaunch/bash-local": "^0.2.0-beta.1",
|
|
120
|
+
"@xlaunch/credentials-local": "^0.2.0-beta.1",
|
|
121
|
+
"@xlaunch/fs-observation-policy": "^0.2.0-beta.1",
|
|
122
|
+
"@xlaunch/fs-sandbox": "^0.2.0-beta.1",
|
|
123
|
+
"@xlaunch/host-frontend-static": "^0.2.0-beta.1",
|
|
124
|
+
"@xlaunch/host-webserver": "^0.2.0-beta.1",
|
|
125
|
+
"@xlaunch/llm": "^0.2.0-beta.1",
|
|
126
|
+
"@xlaunch/llm-mock-server": "^0.2.0-beta.1",
|
|
127
|
+
"@xlaunch/sandbox-local": "^0.2.0-beta.1",
|
|
128
|
+
"@xlaunch/sandbox-policy": "^0.2.0-beta.1",
|
|
129
|
+
"@xlaunch/llm-gateway": "^0.2.0-beta.1",
|
|
130
|
+
"@xlaunch/session": "^0.2.0-beta.1",
|
|
131
|
+
"@xlaunch/session-checkpoint-policy": "^0.2.0-beta.1",
|
|
132
|
+
"@xlaunch/session-persistence-jsonl": "^0.2.0-beta.1",
|
|
133
|
+
"@xlaunch/session-query": "^0.2.0-beta.1",
|
|
134
|
+
"@xlaunch/shell-env": "^0.2.0-beta.1",
|
|
135
|
+
"@xlaunch/loader-smoke": "^0.2.0-beta.1",
|
|
136
|
+
"@xlaunch/settings-file": "^0.2.0-beta.1",
|
|
137
|
+
"@xlaunch/settings": "^0.2.0-beta.1",
|
|
138
|
+
"@xlaunch/subagent-spawn-in-process": "^0.2.0-beta.1",
|
|
139
|
+
"@xlaunch/subagent-fork-in-process": "^0.2.0-beta.1",
|
|
140
|
+
"@xlaunch/llm-replay": "^0.2.0-beta.1",
|
|
141
|
+
"@xlaunch/subagent": "^0.2.0-beta.1",
|
|
142
|
+
"@xlaunch/subprocess-local": "^0.2.0-beta.1",
|
|
143
|
+
"@xlaunch/system-prompt": "^0.2.0-beta.1",
|
|
144
|
+
"@xlaunch/tools": "^0.2.0-beta.1",
|
|
145
|
+
"@xlaunch/user-approval": "^0.2.0-beta.1"
|
|
146
|
+
}
|
|
147
|
+
}
|