@lightpath-dev/cli 0.0.0-stage → 0.0.2
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 +21 -0
- package/README.md +66 -2
- package/dist/bin.d.ts +3 -0
- package/dist/bin.js +9 -0
- package/dist/config.d.ts +35 -0
- package/dist/config.js +86 -0
- package/dist/detect.d.ts +12 -0
- package/dist/detect.js +38 -0
- package/dist/dev.d.ts +25 -0
- package/dist/dev.js +115 -0
- package/dist/doctor.d.ts +3 -0
- package/dist/doctor.js +92 -0
- package/dist/env.d.ts +29 -0
- package/dist/env.js +47 -0
- package/dist/format.d.ts +4 -0
- package/dist/format.js +30 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/main.d.ts +5 -0
- package/dist/main.js +88 -0
- package/lightpath.config.schema.json +79 -0
- package/package.json +58 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lightpath contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,67 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @lightpath-dev/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@lightpath-dev/cli)
|
|
4
|
+
[](https://marketplace.visualstudio.com/items?itemName=lightpath-dev.lightpath-vscode)
|
|
5
|
+
[](https://github.com/Unknown-333/Lightpath/actions/workflows/ci.yml)
|
|
6
|
+
|
|
7
|
+
**Click anything in your running app and see the exact code it ran, frontend and backend, line by line.**
|
|
8
|
+
|
|
9
|
+
No code changes, no test suite, no tracing setup.
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
Requires Node.js 22.12+ and Chrome or Edge.
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npx @lightpath-dev/cli dev -- npm run dev
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Lightpath starts your dev command, finds your app's URL, and opens the app in a browser. Click something, and the code path prints in your terminal:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
◆ button: Checkout 335 ms
|
|
25
|
+
→ POST /api/orders 201
|
|
26
|
+
├─ src/api.ts :25-32, 35-36
|
|
27
|
+
├─ src/App.tsx :6-8, 10, 12, 24-41, 43 ×6
|
|
28
|
+
├─ src/Cart.tsx :11-14, 16-19, 21-28, 30-33, 35-44, 46-48, 50 ×4
|
|
29
|
+
├─ src/money.ts :3-5 ×9
|
|
30
|
+
├─ server/db.ts :26-28, 30-38 ×3
|
|
31
|
+
├─ server/routes/orders.ts :11-14, 19-21, 25, 29, 31-33, 38-40
|
|
32
|
+
├─ server/services/payment.ts :6-7, 10-12
|
|
33
|
+
└─ server/services/pricing.ts :12-19, 21-22, 26-33
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
To see the lines light up in your editor, add the [VS Code extension](https://marketplace.visualstudio.com/items?itemName=lightpath-dev.lightpath-vscode):
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
code --install-extension lightpath-dev.lightpath-vscode
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
To let your coding agent ask "which code handles Checkout?", add the MCP server. For Claude Code:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
claude mcp add lightpath -- npx -y @lightpath-dev/cli mcp
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Setup for Cursor, VS Code and Kiro is in [the main README](https://github.com/Unknown-333/Lightpath#ask-your-coding-agent).
|
|
49
|
+
|
|
50
|
+
## Commands
|
|
51
|
+
|
|
52
|
+
| Command | What it does |
|
|
53
|
+
| ------------------ | -------------------------------------------------------- |
|
|
54
|
+
| `lightpath dev` | Runs your dev command with Lightpath and opens your app |
|
|
55
|
+
| `lightpath mcp` | MCP server over stdio, so coding agents can ask what ran |
|
|
56
|
+
| `lightpath doctor` | Checks Node, the browser, the agent and your config file |
|
|
57
|
+
|
|
58
|
+
Everything stays on your machine: the daemon listens on `127.0.0.1` only and needs a token.
|
|
59
|
+
|
|
60
|
+
## More
|
|
61
|
+
|
|
62
|
+
- [Config file](https://github.com/Unknown-333/Lightpath#config-file): save your flags and hide generated code.
|
|
63
|
+
- [How it works](https://github.com/Unknown-333/Lightpath#how-it-works) and [performance](https://github.com/Unknown-333/Lightpath#performance).
|
|
64
|
+
- [Known limits](https://github.com/Unknown-333/Lightpath#known-limits) and [real apps it was tested on](https://github.com/Unknown-333/Lightpath#tested-on-real-apps).
|
|
65
|
+
- [Issues](https://github.com/Unknown-333/Lightpath/issues) and the [changelog](https://github.com/Unknown-333/Lightpath/blob/main/CHANGELOG.md).
|
|
66
|
+
|
|
67
|
+
MIT licensed.
|
package/dist/bin.d.ts
ADDED
package/dist/bin.js
ADDED
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const CONFIG_FILE = "lightpath.config.json";
|
|
3
|
+
/**
|
|
4
|
+
* `lightpath.config.json` (docs/ARCHITECTURE.md section 9.2). Every key is optional and a flag on
|
|
5
|
+
* the command line wins over it. Unknown keys are errors, so a typo doesn't pass silently.
|
|
6
|
+
*/
|
|
7
|
+
export declare const LightpathConfig: z.ZodObject<{
|
|
8
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
9
|
+
command: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
10
|
+
url: z.ZodOptional<z.ZodString>;
|
|
11
|
+
webRoot: z.ZodOptional<z.ZodString>;
|
|
12
|
+
backend: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
13
|
+
roots: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
14
|
+
ignore: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
15
|
+
settleMs: z.ZodOptional<z.ZodNumber>;
|
|
16
|
+
maxSettleMs: z.ZodOptional<z.ZodNumber>;
|
|
17
|
+
browser: z.ZodOptional<z.ZodBoolean>;
|
|
18
|
+
headless: z.ZodOptional<z.ZodBoolean>;
|
|
19
|
+
}, z.core.$strict>;
|
|
20
|
+
export type LightpathConfig = z.infer<typeof LightpathConfig>;
|
|
21
|
+
export interface LoadedConfig {
|
|
22
|
+
path: string;
|
|
23
|
+
config: LightpathConfig;
|
|
24
|
+
}
|
|
25
|
+
export declare class ConfigError extends Error {
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Reads `lightpath.config.json` from `dir` (or `file` when given). Returns undefined when there is
|
|
29
|
+
* none; throws ConfigError with every problem when it doesn't parse or validate. Relative paths in
|
|
30
|
+
* `webRoot` and `roots` become absolute against the file's folder.
|
|
31
|
+
*/
|
|
32
|
+
export declare function loadConfig(dir: string, file?: string): Promise<LoadedConfig | undefined>;
|
|
33
|
+
/** JSON Schema for editors, from the same definition. A test keeps lightpath.config.schema.json equal to it. */
|
|
34
|
+
export declare function configJsonSchema(): unknown;
|
|
35
|
+
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
export const CONFIG_FILE = 'lightpath.config.json';
|
|
5
|
+
const origin = z.string().refine((s) => {
|
|
6
|
+
try {
|
|
7
|
+
const u = new URL(s);
|
|
8
|
+
return u.protocol === 'http:' || u.protocol === 'https:';
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}, { message: 'must be an http(s) origin, like http://localhost:3001' });
|
|
14
|
+
/**
|
|
15
|
+
* `lightpath.config.json` (docs/ARCHITECTURE.md section 9.2). Every key is optional and a flag on
|
|
16
|
+
* the command line wins over it. Unknown keys are errors, so a typo doesn't pass silently.
|
|
17
|
+
*/
|
|
18
|
+
export const LightpathConfig = z.strictObject({
|
|
19
|
+
$schema: z.string().optional(),
|
|
20
|
+
command: z
|
|
21
|
+
.union([z.string().min(1), z.array(z.string().min(1)).min(1)])
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('Dev command to run when none is given after --. A string runs in a shell.'),
|
|
24
|
+
url: origin.optional().describe('App URL; detected from the command output when omitted'),
|
|
25
|
+
webRoot: z.string().min(1).optional().describe('Folder the dev server serves from, relative to this file'),
|
|
26
|
+
backend: z.array(origin).optional().describe('Remote backend origins to tag with the trace header'),
|
|
27
|
+
roots: z.array(z.string().min(1)).min(1).optional().describe('Workspace roots, relative to this file'),
|
|
28
|
+
ignore: z
|
|
29
|
+
.array(z.string())
|
|
30
|
+
.optional()
|
|
31
|
+
.describe('Globs for workspace files to leave out of traces, like "src/generated/**"'),
|
|
32
|
+
settleMs: z.number().int().min(50).max(10_000).optional().describe('Quiet time that ends a click (ms)'),
|
|
33
|
+
maxSettleMs: z.number().int().min(500).max(60_000).optional().describe('Longest a click can last (ms)'),
|
|
34
|
+
browser: z.boolean().optional().describe('false: trace backend requests only'),
|
|
35
|
+
headless: z.boolean().optional().describe('Run the browser without a window'),
|
|
36
|
+
});
|
|
37
|
+
export class ConfigError extends Error {
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Reads `lightpath.config.json` from `dir` (or `file` when given). Returns undefined when there is
|
|
41
|
+
* none; throws ConfigError with every problem when it doesn't parse or validate. Relative paths in
|
|
42
|
+
* `webRoot` and `roots` become absolute against the file's folder.
|
|
43
|
+
*/
|
|
44
|
+
export async function loadConfig(dir, file) {
|
|
45
|
+
const path = resolve(dir, file ?? CONFIG_FILE);
|
|
46
|
+
let raw;
|
|
47
|
+
try {
|
|
48
|
+
raw = await readFile(path, 'utf8');
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
if (err.code === 'ENOENT' && file === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
throw new ConfigError(`can't read ${path}: ${err.message}`);
|
|
54
|
+
}
|
|
55
|
+
let json;
|
|
56
|
+
try {
|
|
57
|
+
json = JSON.parse(raw.replace(/^\uFEFF/, ''));
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
throw new ConfigError(`${path} isn't valid JSON: ${err.message}`);
|
|
61
|
+
}
|
|
62
|
+
const parsed = LightpathConfig.safeParse(json);
|
|
63
|
+
if (!parsed.success) {
|
|
64
|
+
const problems = parsed.error.issues.map((i) => {
|
|
65
|
+
const where = i.path.length ? i.path.join('.') : '(top level)';
|
|
66
|
+
return ` ${where}: ${i.message}`;
|
|
67
|
+
});
|
|
68
|
+
throw new ConfigError(`${path} has problems:\n${problems.join('\n')}`);
|
|
69
|
+
}
|
|
70
|
+
const base = dirname(path);
|
|
71
|
+
const config = { ...parsed.data };
|
|
72
|
+
if (config.webRoot)
|
|
73
|
+
config.webRoot = resolve(base, config.webRoot);
|
|
74
|
+
if (config.roots)
|
|
75
|
+
config.roots = config.roots.map((r) => resolve(base, r));
|
|
76
|
+
if (config.backend)
|
|
77
|
+
config.backend = config.backend.map((b) => new URL(b).origin);
|
|
78
|
+
if (config.url)
|
|
79
|
+
config.url = new URL(config.url).href;
|
|
80
|
+
return { path, config };
|
|
81
|
+
}
|
|
82
|
+
/** JSON Schema for editors, from the same definition. A test keeps lightpath.config.schema.json equal to it. */
|
|
83
|
+
export function configJsonSchema() {
|
|
84
|
+
return z.toJSONSchema(LightpathConfig, { target: 'draft-7' });
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=config.js.map
|
package/dist/detect.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finds the first local dev-server URL in a chunk of output, e.g. Vite's
|
|
3
|
+
* `➜ Local: http://localhost:5173/`. Returns it without a trailing slash.
|
|
4
|
+
*/
|
|
5
|
+
export declare function findLocalUrl(output: string): string | undefined;
|
|
6
|
+
/** Line-buffers a stream of chunks and reports the first local URL seen. */
|
|
7
|
+
export declare class UrlWatcher {
|
|
8
|
+
#private;
|
|
9
|
+
get found(): string | undefined;
|
|
10
|
+
push(chunk: string): string | undefined;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=detect.d.ts.map
|
package/dist/detect.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: matching ANSI escape sequences is the point.
|
|
2
|
+
const ANSI = /\u001b\[[0-9;]*[A-Za-z]/g;
|
|
3
|
+
const LOCAL_URL = /\bhttps?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):\d{2,5}(?:\/[^\s'")\]]*)?/;
|
|
4
|
+
/**
|
|
5
|
+
* Finds the first local dev-server URL in a chunk of output, e.g. Vite's
|
|
6
|
+
* `➜ Local: http://localhost:5173/`. Returns it without a trailing slash.
|
|
7
|
+
*/
|
|
8
|
+
export function findLocalUrl(output) {
|
|
9
|
+
const match = LOCAL_URL.exec(output.replace(ANSI, ''));
|
|
10
|
+
return match?.[0].replace(/\/$/, '');
|
|
11
|
+
}
|
|
12
|
+
/** Line-buffers a stream of chunks and reports the first local URL seen. */
|
|
13
|
+
export class UrlWatcher {
|
|
14
|
+
#buffer = '';
|
|
15
|
+
#found;
|
|
16
|
+
get found() {
|
|
17
|
+
return this.#found;
|
|
18
|
+
}
|
|
19
|
+
push(chunk) {
|
|
20
|
+
if (this.#found)
|
|
21
|
+
return undefined;
|
|
22
|
+
this.#buffer += chunk;
|
|
23
|
+
const lines = this.#buffer.split(/\r?\n/);
|
|
24
|
+
this.#buffer = lines.pop() ?? '';
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
const url = findLocalUrl(line);
|
|
27
|
+
if (url) {
|
|
28
|
+
this.#found = url;
|
|
29
|
+
return url;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Keep memory bounded if the child prints a huge line without a newline.
|
|
33
|
+
if (this.#buffer.length > 10_000)
|
|
34
|
+
this.#buffer = this.#buffer.slice(-1000);
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=detect.js.map
|
package/dist/dev.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface DevOptions {
|
|
2
|
+
/**
|
|
3
|
+
* The user's dev command and its arguments, e.g. ['npm', 'run', 'dev'], or one string that runs in
|
|
4
|
+
* a shell (the `command` key in lightpath.config.json).
|
|
5
|
+
*/
|
|
6
|
+
command: string[] | string;
|
|
7
|
+
/** App URL; detected from the command's output when omitted. */
|
|
8
|
+
url?: string | undefined;
|
|
9
|
+
/** Directory Vite (or another dev server) serves from. Defaults to cwd. */
|
|
10
|
+
webRoot?: string | undefined;
|
|
11
|
+
/** Extra origins to tag with the trace header, besides the app's own. */
|
|
12
|
+
backend?: string[] | undefined;
|
|
13
|
+
roots?: string[] | undefined;
|
|
14
|
+
/** Globs for workspace files to leave out of traces. */
|
|
15
|
+
ignore?: string[] | undefined;
|
|
16
|
+
settleMs?: number | undefined;
|
|
17
|
+
maxSettleMs?: number | undefined;
|
|
18
|
+
browser?: boolean | undefined;
|
|
19
|
+
headless?: boolean | undefined;
|
|
20
|
+
debug?: boolean | undefined;
|
|
21
|
+
cwd?: string | undefined;
|
|
22
|
+
}
|
|
23
|
+
/** `lightpath dev -- <command>` (docs/ARCHITECTURE.md section 9.2). */
|
|
24
|
+
export declare function dev(options: DevOptions): Promise<number>;
|
|
25
|
+
//# sourceMappingURL=dev.d.ts.map
|
package/dist/dev.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { BrowserCollector } from '@lightpath-dev/browser';
|
|
4
|
+
import { startDaemon } from '@lightpath-dev/daemon';
|
|
5
|
+
import { registerUrl } from '@lightpath-dev/node-agent';
|
|
6
|
+
import pc from 'picocolors';
|
|
7
|
+
import { UrlWatcher } from './detect.js';
|
|
8
|
+
import { agentEnv, nodeVersionProblem, toShellCommand } from './env.js';
|
|
9
|
+
import { formatTrace } from './format.js';
|
|
10
|
+
const say = (msg) => process.stderr.write(`${pc.magenta('lightpath')} ${msg}\n`);
|
|
11
|
+
/** `lightpath dev -- <command>` (docs/ARCHITECTURE.md section 9.2). */
|
|
12
|
+
export async function dev(options) {
|
|
13
|
+
const problem = nodeVersionProblem(process.version);
|
|
14
|
+
if (problem) {
|
|
15
|
+
say(pc.red(problem));
|
|
16
|
+
return 1;
|
|
17
|
+
}
|
|
18
|
+
const shellLine = typeof options.command === 'string' ? options.command.trim() : undefined;
|
|
19
|
+
const [cmd, ...args] = typeof options.command === 'string' ? [] : options.command;
|
|
20
|
+
if (!cmd && !shellLine) {
|
|
21
|
+
say(pc.red('missing command. Usage: lightpath dev -- npm run dev (or set "command" in lightpath.config.json)'));
|
|
22
|
+
return 1;
|
|
23
|
+
}
|
|
24
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
25
|
+
const roots = (options.roots?.length ? options.roots : [cwd]).map((r) => resolve(cwd, r));
|
|
26
|
+
const daemon = await startDaemon({
|
|
27
|
+
workspaceRoots: roots,
|
|
28
|
+
...(options.ignore?.length ? { ignore: options.ignore } : {}),
|
|
29
|
+
...(options.debug ? { log: say } : {}),
|
|
30
|
+
});
|
|
31
|
+
say(`daemon on ${pc.dim(daemon.url)}`);
|
|
32
|
+
const unsubscribe = daemon.store.subscribe((trace) => {
|
|
33
|
+
if (trace.endedAt !== undefined)
|
|
34
|
+
process.stdout.write(`\n${formatTrace(trace)}\n`);
|
|
35
|
+
});
|
|
36
|
+
const env = agentEnv(process.env, {
|
|
37
|
+
daemonUrl: daemon.url,
|
|
38
|
+
token: daemon.token,
|
|
39
|
+
roots,
|
|
40
|
+
registerUrl,
|
|
41
|
+
debug: options.debug,
|
|
42
|
+
});
|
|
43
|
+
const spawnOptions = { cwd, env, stdio: ['inherit', 'pipe', 'pipe'] };
|
|
44
|
+
// Windows needs a shell so `npm`, `pnpm`, and other `.cmd` shims resolve.
|
|
45
|
+
const child = shellLine
|
|
46
|
+
? spawn(shellLine, { ...spawnOptions, shell: true })
|
|
47
|
+
: process.platform === 'win32'
|
|
48
|
+
? spawn(toShellCommand([cmd, ...args]), { ...spawnOptions, shell: true })
|
|
49
|
+
: spawn(cmd, args, spawnOptions);
|
|
50
|
+
let collector;
|
|
51
|
+
const openBrowser = async (url) => {
|
|
52
|
+
if (options.browser === false || collector)
|
|
53
|
+
return;
|
|
54
|
+
const origin = new URL(url).origin;
|
|
55
|
+
daemon.correlator.addDevServer(origin, resolve(cwd, options.webRoot ?? '.'));
|
|
56
|
+
collector = new BrowserCollector({
|
|
57
|
+
url,
|
|
58
|
+
backendOrigins: [origin, ...(options.backend ?? [])],
|
|
59
|
+
...(options.settleMs !== undefined ? { settleMs: options.settleMs } : {}),
|
|
60
|
+
...(options.maxSettleMs !== undefined ? { maxSettleMs: options.maxSettleMs } : {}),
|
|
61
|
+
...(options.headless !== undefined ? { headless: options.headless } : {}),
|
|
62
|
+
send: (m) => ingestLocal(daemon, m),
|
|
63
|
+
notice: (m) => say(pc.yellow(m)),
|
|
64
|
+
...(options.debug ? { log: say } : {}),
|
|
65
|
+
});
|
|
66
|
+
try {
|
|
67
|
+
await collector.start();
|
|
68
|
+
say(`watching ${pc.cyan(url)}. Click anything in the browser window.`);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
say(pc.red(err.message));
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const watcher = new UrlWatcher();
|
|
75
|
+
const pipe = (stream, out) => {
|
|
76
|
+
stream?.on('data', (chunk) => {
|
|
77
|
+
out.write(chunk);
|
|
78
|
+
const url = options.url ? undefined : watcher.push(chunk.toString());
|
|
79
|
+
if (url)
|
|
80
|
+
void openBrowser(url);
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
pipe(child.stdout, process.stdout);
|
|
84
|
+
pipe(child.stderr, process.stderr);
|
|
85
|
+
if (options.url)
|
|
86
|
+
void openBrowser(options.url);
|
|
87
|
+
const code = await waitForExit(child);
|
|
88
|
+
unsubscribe();
|
|
89
|
+
await collector?.stop();
|
|
90
|
+
await daemon.close();
|
|
91
|
+
return code;
|
|
92
|
+
}
|
|
93
|
+
/** The collector runs in this process, so its messages skip HTTP and go straight to the correlator. */
|
|
94
|
+
function ingestLocal(daemon, message) {
|
|
95
|
+
daemon.correlator.ingest('browser', message);
|
|
96
|
+
}
|
|
97
|
+
function waitForExit(child) {
|
|
98
|
+
return new Promise((resolveExit) => {
|
|
99
|
+
const forward = (signal) => () => child.kill(signal);
|
|
100
|
+
const onInt = forward('SIGINT');
|
|
101
|
+
const onTerm = forward('SIGTERM');
|
|
102
|
+
process.on('SIGINT', onInt);
|
|
103
|
+
process.on('SIGTERM', onTerm);
|
|
104
|
+
child.on('exit', (code, signal) => {
|
|
105
|
+
process.off('SIGINT', onInt);
|
|
106
|
+
process.off('SIGTERM', onTerm);
|
|
107
|
+
resolveExit(code ?? (signal ? 130 : 0));
|
|
108
|
+
});
|
|
109
|
+
child.on('error', (err) => {
|
|
110
|
+
say(pc.red(`could not start command: ${err.message}`));
|
|
111
|
+
resolveExit(127);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=dev.js.map
|
package/dist/doctor.d.ts
ADDED
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { launchBrowser } from '@lightpath-dev/browser';
|
|
5
|
+
import { findDiscovery } from '@lightpath-dev/daemon';
|
|
6
|
+
import { registerUrl } from '@lightpath-dev/node-agent';
|
|
7
|
+
import { DISCOVERY_FILE } from '@lightpath-dev/protocol';
|
|
8
|
+
import pc from 'picocolors';
|
|
9
|
+
import { CONFIG_FILE, ConfigError, loadConfig } from './config.js';
|
|
10
|
+
import { nodeVersionProblem } from './env.js';
|
|
11
|
+
/** `lightpath doctor`: checks the environment and explains what to fix. */
|
|
12
|
+
export async function doctor(cwd, configFile) {
|
|
13
|
+
const checks = [
|
|
14
|
+
{
|
|
15
|
+
name: 'Node.js version',
|
|
16
|
+
run: async () => {
|
|
17
|
+
const problem = nodeVersionProblem(process.version);
|
|
18
|
+
return { ok: !problem, detail: problem ?? process.version };
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'Chromium-based browser',
|
|
23
|
+
run: async () => {
|
|
24
|
+
try {
|
|
25
|
+
const browser = await launchBrowser({ headless: true });
|
|
26
|
+
const version = browser.version();
|
|
27
|
+
await browser.close();
|
|
28
|
+
return { ok: true, detail: version };
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
return { ok: false, detail: err.message };
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'Node agent',
|
|
37
|
+
run: async () => {
|
|
38
|
+
const path = fileURLToPath(registerUrl);
|
|
39
|
+
return existsSync(path)
|
|
40
|
+
? { ok: true, detail: path }
|
|
41
|
+
: { ok: false, detail: `missing ${path}; reinstall @lightpath-dev/cli` };
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'Config file',
|
|
46
|
+
run: async () => {
|
|
47
|
+
try {
|
|
48
|
+
const loaded = await loadConfig(cwd, configFile);
|
|
49
|
+
if (!loaded)
|
|
50
|
+
return { ok: true, detail: `none (optional; see ${CONFIG_FILE} in the README)` };
|
|
51
|
+
const keys = Object.keys(loaded.config).filter((k) => k !== '$schema');
|
|
52
|
+
return { ok: true, detail: `${loaded.path} (${keys.join(', ') || 'empty'})` };
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
if (err instanceof ConfigError)
|
|
56
|
+
return { ok: false, detail: err.message };
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'Daemon for this workspace',
|
|
63
|
+
run: async () => {
|
|
64
|
+
// Look where `lightpath mcp` looks: here, then each parent (D-102).
|
|
65
|
+
const found = await findDiscovery(cwd);
|
|
66
|
+
if (!found)
|
|
67
|
+
return { ok: true, detail: 'not running (start it with lightpath dev)' };
|
|
68
|
+
const { root, info } = found;
|
|
69
|
+
const where = root === resolve(cwd) ? '' : ` (started in ${root})`;
|
|
70
|
+
try {
|
|
71
|
+
const res = await fetch(`http://127.0.0.1:${info.port}/v1/health`);
|
|
72
|
+
return { ok: res.ok, detail: `running on port ${info.port}, pid ${info.pid}${where}` };
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
detail: `stale ${join(root, DISCOVERY_FILE)} (port ${info.port} not answering); delete it`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
];
|
|
83
|
+
let failed = 0;
|
|
84
|
+
for (const check of checks) {
|
|
85
|
+
const { ok, detail } = await check.run();
|
|
86
|
+
if (!ok)
|
|
87
|
+
failed++;
|
|
88
|
+
process.stdout.write(`${ok ? pc.green('✓') : pc.red('✗')} ${check.name}: ${detail}\n`);
|
|
89
|
+
}
|
|
90
|
+
return failed === 0 ? 0 : 1;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=doctor.js.map
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adds `--require="<path>"` to NODE_OPTIONS without dropping the user's own options (ARCHITECTURE 10).
|
|
3
|
+
*
|
|
4
|
+
* `--require`, not `--import` (D-085): any `--import` makes Node run the main file through its ES
|
|
5
|
+
* module loader, which skips `require` hooks, so `ts-node` (and other tools that call
|
|
6
|
+
* `Module.runMain`) failed to start. Node 22.12+ can `--require` the agent's ES module file.
|
|
7
|
+
* NODE_OPTIONS eats backslashes inside quotes on Windows (measured), so the path uses forward
|
|
8
|
+
* slashes; quoting handles spaces.
|
|
9
|
+
*/
|
|
10
|
+
export declare function withRequire(existing: string | undefined, registerUrl: string): string;
|
|
11
|
+
export interface AgentEnvInput {
|
|
12
|
+
daemonUrl: string;
|
|
13
|
+
token: string;
|
|
14
|
+
roots: string[];
|
|
15
|
+
registerUrl: string;
|
|
16
|
+
skip?: string | undefined;
|
|
17
|
+
debug?: boolean | undefined;
|
|
18
|
+
}
|
|
19
|
+
/** Environment for the user's dev command, so every Node process it starts loads the agent. */
|
|
20
|
+
export declare function agentEnv(base: NodeJS.ProcessEnv, input: AgentEnvInput): NodeJS.ProcessEnv;
|
|
21
|
+
/** Node versions the agent supports: 22.12+ (ADR 0002). */
|
|
22
|
+
export declare function nodeVersionProblem(version: string): string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Joins the user's command for `spawn(line, { shell: true })` on Windows, where `npm` and other
|
|
25
|
+
* `.cmd` shims need a shell. Node 24 deprecates passing an argument array with `shell: true`
|
|
26
|
+
* (DEP0190), so arguments are quoted here. The input is the user's own command line.
|
|
27
|
+
*/
|
|
28
|
+
export declare function toShellCommand(argv: readonly string[]): string;
|
|
29
|
+
//# sourceMappingURL=env.d.ts.map
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { AgentEnv } from '@lightpath-dev/protocol';
|
|
3
|
+
/**
|
|
4
|
+
* Adds `--require="<path>"` to NODE_OPTIONS without dropping the user's own options (ARCHITECTURE 10).
|
|
5
|
+
*
|
|
6
|
+
* `--require`, not `--import` (D-085): any `--import` makes Node run the main file through its ES
|
|
7
|
+
* module loader, which skips `require` hooks, so `ts-node` (and other tools that call
|
|
8
|
+
* `Module.runMain`) failed to start. Node 22.12+ can `--require` the agent's ES module file.
|
|
9
|
+
* NODE_OPTIONS eats backslashes inside quotes on Windows (measured), so the path uses forward
|
|
10
|
+
* slashes; quoting handles spaces.
|
|
11
|
+
*/
|
|
12
|
+
export function withRequire(existing, registerUrl) {
|
|
13
|
+
const path = fileURLToPath(registerUrl).replace(/\\/g, '/');
|
|
14
|
+
const flag = `--require="${path}"`;
|
|
15
|
+
const current = existing?.trim() ?? '';
|
|
16
|
+
if (current.includes(path))
|
|
17
|
+
return current;
|
|
18
|
+
return current ? `${current} ${flag}` : flag;
|
|
19
|
+
}
|
|
20
|
+
/** Environment for the user's dev command, so every Node process it starts loads the agent. */
|
|
21
|
+
export function agentEnv(base, input) {
|
|
22
|
+
return {
|
|
23
|
+
...base,
|
|
24
|
+
NODE_OPTIONS: withRequire(base.NODE_OPTIONS, input.registerUrl),
|
|
25
|
+
[AgentEnv.url]: input.daemonUrl,
|
|
26
|
+
[AgentEnv.token]: input.token,
|
|
27
|
+
[AgentEnv.roots]: input.roots.join(','),
|
|
28
|
+
...(input.skip ? { [AgentEnv.skip]: input.skip } : {}),
|
|
29
|
+
...(input.debug ? { [AgentEnv.debug]: '1' } : {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Node versions the agent supports: 22.12+ (ADR 0002). */
|
|
33
|
+
export function nodeVersionProblem(version) {
|
|
34
|
+
const [major = 0, minor = 0] = version.replace(/^v/, '').split('.').map(Number);
|
|
35
|
+
if (major > 22 || (major === 22 && minor >= 12))
|
|
36
|
+
return undefined;
|
|
37
|
+
return `Lightpath needs Node.js 22.12 or newer; this is ${version}.`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Joins the user's command for `spawn(line, { shell: true })` on Windows, where `npm` and other
|
|
41
|
+
* `.cmd` shims need a shell. Node 24 deprecates passing an argument array with `shell: true`
|
|
42
|
+
* (DEP0190), so arguments are quoted here. The input is the user's own command line.
|
|
43
|
+
*/
|
|
44
|
+
export function toShellCommand(argv) {
|
|
45
|
+
return argv.map((a) => (/^[\w./:=@+-]+$/.test(a) ? a : `"${a.replace(/"/g, '""')}"`)).join(' ');
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=env.js.map
|
package/dist/format.d.ts
ADDED
package/dist/format.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { lineRanges } from '@lightpath-dev/mcp';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
const COLLECTOR_COLOR = { browser: pc.cyan, node: pc.green, python: pc.yellow };
|
|
4
|
+
/** Prints one trace as a tree of files and line ranges, for `lightpath dev` (ARCHITECTURE 9.2). */
|
|
5
|
+
export function formatTrace(trace, color = true) {
|
|
6
|
+
const c = color ? pc : pc.createColors(false);
|
|
7
|
+
const duration = trace.endedAt ? ` ${c.dim(`${Math.max(0, trace.endedAt - trace.startedAt)} ms`)}` : '';
|
|
8
|
+
const lines = [`${c.bold('◆')} ${c.bold(trace.label)}${duration} ${c.dim(trace.id)}`];
|
|
9
|
+
for (const r of trace.requests) {
|
|
10
|
+
const status = r.status === undefined ? '' : r.status >= 400 ? c.red(` ${r.status}`) : c.dim(` ${r.status}`);
|
|
11
|
+
lines.push(` ${c.dim('→')} ${r.method} ${r.path}${status}${r.overlapped ? c.yellow(' (overlapped)') : ''}`);
|
|
12
|
+
}
|
|
13
|
+
// Files where only periodic code ran (timers, polling) go last and dimmed; they're rarely the answer.
|
|
14
|
+
const files = [...trace.files.filter((f) => !f.background), ...trace.files.filter((f) => f.background)];
|
|
15
|
+
files.forEach((f, i) => {
|
|
16
|
+
const branch = i === files.length - 1 ? '└─' : '├─';
|
|
17
|
+
const paint = f.background ? c.dim : color ? COLLECTOR_COLOR[f.collector] : (s) => s;
|
|
18
|
+
const hottest = Math.max(...f.lines.map((l) => l.count));
|
|
19
|
+
const hot = hottest > 1 ? c.dim(` ×${hottest}`) : '';
|
|
20
|
+
const note = f.background ? c.dim(' (background)') : '';
|
|
21
|
+
lines.push(` ${c.dim(branch)} ${paint(f.path)} ${c.dim(`:${lineRanges(f.lines)}`)}${hot}${note}`);
|
|
22
|
+
});
|
|
23
|
+
if (trace.files.length === 0)
|
|
24
|
+
lines.push(` ${c.dim('no workspace code ran')}`);
|
|
25
|
+
const hidden = trace.dropped.reduce((n, d) => n + d.count, 0);
|
|
26
|
+
if (hidden > 0)
|
|
27
|
+
lines.push(` ${c.dim(`${hidden} scripts hidden (${trace.dropped.map((d) => d.reason).join(', ')})`)}`);
|
|
28
|
+
return lines.join('\n');
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=format.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { CONFIG_FILE, ConfigError, configJsonSchema, LightpathConfig, type LoadedConfig, loadConfig, } from './config.js';
|
|
2
|
+
export { findLocalUrl, UrlWatcher } from './detect.js';
|
|
3
|
+
export { type DevOptions, dev } from './dev.js';
|
|
4
|
+
export { doctor } from './doctor.js';
|
|
5
|
+
export { agentEnv, nodeVersionProblem, withRequire } from './env.js';
|
|
6
|
+
export { formatTrace } from './format.js';
|
|
7
|
+
export { main, VERSION } from './main.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { CONFIG_FILE, ConfigError, configJsonSchema, LightpathConfig, loadConfig, } from './config.js';
|
|
2
|
+
export { findLocalUrl, UrlWatcher } from './detect.js';
|
|
3
|
+
export { dev } from './dev.js';
|
|
4
|
+
export { doctor } from './doctor.js';
|
|
5
|
+
export { agentEnv, nodeVersionProblem, withRequire } from './env.js';
|
|
6
|
+
export { formatTrace } from './format.js';
|
|
7
|
+
export { main, VERSION } from './main.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** This package's version, read from package.json (one folder up from both src/ and dist/). */
|
|
2
|
+
export declare const VERSION: string;
|
|
3
|
+
/** Builds the CLI. Returns the process exit code. */
|
|
4
|
+
export declare function main(argv: string[]): Promise<number>;
|
|
5
|
+
//# sourceMappingURL=main.d.ts.map
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { runMcpServer } from '@lightpath-dev/mcp';
|
|
3
|
+
import { cac } from 'cac';
|
|
4
|
+
import { CONFIG_FILE, ConfigError, loadConfig } from './config.js';
|
|
5
|
+
import { dev } from './dev.js';
|
|
6
|
+
import { doctor } from './doctor.js';
|
|
7
|
+
/** This package's version, read from package.json (one folder up from both src/ and dist/). */
|
|
8
|
+
export const VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
9
|
+
const list = (v) => v === undefined
|
|
10
|
+
? undefined
|
|
11
|
+
: (Array.isArray(v) ? v : [v]).flatMap((s) => String(s).split(',')).filter(Boolean);
|
|
12
|
+
/** Builds the CLI. Returns the process exit code. */
|
|
13
|
+
export async function main(argv) {
|
|
14
|
+
const cli = cac('lightpath');
|
|
15
|
+
let result = Promise.resolve(0);
|
|
16
|
+
cli
|
|
17
|
+
.command('dev', 'Run your dev command with Lightpath, open your app, and print the code each click ran')
|
|
18
|
+
.usage('dev [options] -- <command...>')
|
|
19
|
+
.option('--url <url>', 'App URL (default: detected from the command output)')
|
|
20
|
+
.option('--web-root <dir>', 'Directory the dev server serves from (default: current directory)')
|
|
21
|
+
.option('--backend <origin>', 'Extra backend origin to tag with the trace header (repeatable)')
|
|
22
|
+
.option('--root <dir>', 'Workspace root to show files from (repeatable; default: current directory)')
|
|
23
|
+
.option('--ignore <glob>', 'Leave matching workspace files out of traces (repeatable)')
|
|
24
|
+
.option('--config <file>', `Config file (default: ${CONFIG_FILE} in the current directory, if any)`)
|
|
25
|
+
.option('--no-browser', "Don't open a browser; trace backend requests only")
|
|
26
|
+
.option('--headless', 'Run the browser without a window')
|
|
27
|
+
.option('--debug', 'Print Lightpath internals')
|
|
28
|
+
.example('lightpath dev -- npm run dev')
|
|
29
|
+
.example('lightpath dev --url http://localhost:5173 -- pnpm dev')
|
|
30
|
+
.example(`lightpath dev (runs "command" from ${CONFIG_FILE})`)
|
|
31
|
+
.action((options) => {
|
|
32
|
+
result = (async () => {
|
|
33
|
+
let loaded;
|
|
34
|
+
try {
|
|
35
|
+
loaded = await loadConfig(process.cwd(), options.config);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
if (!(err instanceof ConfigError))
|
|
39
|
+
throw err;
|
|
40
|
+
process.stderr.write(`lightpath: ${err.message}\n`);
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
const config = loaded?.config ?? {};
|
|
44
|
+
const flagCommand = options['--'] ?? [];
|
|
45
|
+
// cac fills in browser: true for --no-browser's default, so read the flag from argv.
|
|
46
|
+
const end = argv.indexOf('--');
|
|
47
|
+
const noBrowser = (end < 0 ? argv : argv.slice(0, end)).includes('--no-browser');
|
|
48
|
+
return dev({
|
|
49
|
+
command: flagCommand.length > 0 ? flagCommand : (config.command ?? []),
|
|
50
|
+
url: options.url ?? config.url,
|
|
51
|
+
webRoot: options.webRoot ?? config.webRoot,
|
|
52
|
+
backend: list(options.backend) ?? config.backend,
|
|
53
|
+
roots: list(options.root) ?? config.roots,
|
|
54
|
+
ignore: [
|
|
55
|
+
...(config.ignore ?? []),
|
|
56
|
+
...(list(options.ignore) ?? []),
|
|
57
|
+
],
|
|
58
|
+
settleMs: config.settleMs,
|
|
59
|
+
maxSettleMs: config.maxSettleMs,
|
|
60
|
+
browser: noBrowser ? false : config.browser,
|
|
61
|
+
headless: options.headless === true ? true : config.headless,
|
|
62
|
+
debug: options.debug,
|
|
63
|
+
});
|
|
64
|
+
})();
|
|
65
|
+
});
|
|
66
|
+
cli
|
|
67
|
+
.command('mcp', 'Run the MCP server over stdio for coding agents (reads .lightpath/daemon.json)')
|
|
68
|
+
.option('--root <dir>', 'Where to look for .lightpath/daemon.json, then in each parent (default: current directory)')
|
|
69
|
+
.action((options) => {
|
|
70
|
+
result = runMcpServer({ workspaceRoot: options.root ?? process.cwd() }).then(() => new Promise(() => { }));
|
|
71
|
+
});
|
|
72
|
+
cli
|
|
73
|
+
.command('doctor', 'Check that Lightpath can run here')
|
|
74
|
+
.option('--config <file>', `Config file to check (default: ${CONFIG_FILE})`)
|
|
75
|
+
.action((options) => {
|
|
76
|
+
result = doctor(process.cwd(), options.config);
|
|
77
|
+
});
|
|
78
|
+
cli.help();
|
|
79
|
+
cli.version(VERSION);
|
|
80
|
+
cli.parse(argv, { run: false });
|
|
81
|
+
if (!cli.matchedCommand && !cli.options.help && !cli.options.version) {
|
|
82
|
+
cli.outputHelp();
|
|
83
|
+
return cli.args.length > 0 ? 1 : 0;
|
|
84
|
+
}
|
|
85
|
+
await cli.runMatchedCommand();
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"$schema": {
|
|
6
|
+
"type": "string"
|
|
7
|
+
},
|
|
8
|
+
"command": {
|
|
9
|
+
"description": "Dev command to run when none is given after --. A string runs in a shell.",
|
|
10
|
+
"anyOf": [
|
|
11
|
+
{
|
|
12
|
+
"type": "string",
|
|
13
|
+
"minLength": 1
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"minItems": 1,
|
|
17
|
+
"type": "array",
|
|
18
|
+
"items": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"minLength": 1
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"url": {
|
|
26
|
+
"description": "App URL; detected from the command output when omitted",
|
|
27
|
+
"type": "string"
|
|
28
|
+
},
|
|
29
|
+
"webRoot": {
|
|
30
|
+
"description": "Folder the dev server serves from, relative to this file",
|
|
31
|
+
"type": "string",
|
|
32
|
+
"minLength": 1
|
|
33
|
+
},
|
|
34
|
+
"backend": {
|
|
35
|
+
"description": "Remote backend origins to tag with the trace header",
|
|
36
|
+
"type": "array",
|
|
37
|
+
"items": {
|
|
38
|
+
"type": "string"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"roots": {
|
|
42
|
+
"description": "Workspace roots, relative to this file",
|
|
43
|
+
"minItems": 1,
|
|
44
|
+
"type": "array",
|
|
45
|
+
"items": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"minLength": 1
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"ignore": {
|
|
51
|
+
"description": "Globs for workspace files to leave out of traces, like \"src/generated/**\"",
|
|
52
|
+
"type": "array",
|
|
53
|
+
"items": {
|
|
54
|
+
"type": "string"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"settleMs": {
|
|
58
|
+
"description": "Quiet time that ends a click (ms)",
|
|
59
|
+
"type": "integer",
|
|
60
|
+
"minimum": 50,
|
|
61
|
+
"maximum": 10000
|
|
62
|
+
},
|
|
63
|
+
"maxSettleMs": {
|
|
64
|
+
"description": "Longest a click can last (ms)",
|
|
65
|
+
"type": "integer",
|
|
66
|
+
"minimum": 500,
|
|
67
|
+
"maximum": 60000
|
|
68
|
+
},
|
|
69
|
+
"browser": {
|
|
70
|
+
"description": "false: trace backend requests only",
|
|
71
|
+
"type": "boolean"
|
|
72
|
+
},
|
|
73
|
+
"headless": {
|
|
74
|
+
"description": "Run the browser without a window",
|
|
75
|
+
"type": "boolean"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"additionalProperties": false
|
|
79
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightpath-dev/cli",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Click anything in your running app and see which frontend and backend lines ran",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"coverage",
|
|
7
|
+
"runtime",
|
|
8
|
+
"trace",
|
|
9
|
+
"debug",
|
|
10
|
+
"click",
|
|
11
|
+
"devtools",
|
|
12
|
+
"mcp"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"homepage": "https://github.com/Unknown-333/Lightpath#readme",
|
|
16
|
+
"bugs": "https://github.com/Unknown-333/Lightpath/issues",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/Unknown-333/Lightpath.git",
|
|
20
|
+
"directory": "packages/cli"
|
|
21
|
+
},
|
|
22
|
+
"type": "module",
|
|
23
|
+
"bin": {
|
|
24
|
+
"lightpath": "./dist/bin.js"
|
|
25
|
+
},
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"lightpath.config.schema.json",
|
|
35
|
+
"!dist/.tsbuildinfo",
|
|
36
|
+
"!dist/**/*.map"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22.12"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@lightpath-dev/browser": "0.0.2",
|
|
46
|
+
"@lightpath-dev/daemon": "0.0.1",
|
|
47
|
+
"@lightpath-dev/mcp": "0.0.1",
|
|
48
|
+
"@lightpath-dev/node-agent": "0.0.1",
|
|
49
|
+
"@lightpath-dev/protocol": "0.0.1",
|
|
50
|
+
"cac": "7.1.0",
|
|
51
|
+
"picocolors": "1.1.1",
|
|
52
|
+
"zod": "4.6.7"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@modelcontextprotocol/sdk": "1.32.2"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"typecheck": "tsc -p test"
|
|
60
|
+
}
|
|
6
61
|
}
|