@paneui/cli 0.0.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 +21 -0
- package/README.md +61 -0
- package/dist/argv.js +52 -0
- package/dist/commands/artifact.js +331 -0
- package/dist/commands/config.js +31 -0
- package/dist/commands/create.js +196 -0
- package/dist/commands/delete.js +31 -0
- package/dist/commands/keys.js +76 -0
- package/dist/commands/logout.js +25 -0
- package/dist/commands/register.js +79 -0
- package/dist/commands/send.js +58 -0
- package/dist/commands/state.js +34 -0
- package/dist/commands/watch.js +138 -0
- package/dist/config.js +77 -0
- package/dist/index.js +159 -0
- package/dist/input.js +42 -0
- package/dist/output.js +40 -0
- package/dist/store.js +64 -0
- package/package.json +51 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Relay connection config: PANE_URL / PANE_API_KEY from the environment,
|
|
2
|
+
// overridable per-invocation with --url / --api-key.
|
|
3
|
+
import { PaneClient } from "@paneui/core";
|
|
4
|
+
import { fail } from "./output.js";
|
|
5
|
+
import { readStore, storePath } from "./store.js";
|
|
6
|
+
/**
|
|
7
|
+
* Resolve url + apiKey and report the SOURCE of each, WITHOUT making a network
|
|
8
|
+
* call and WITHOUT failing on a missing value (unlike `resolveConfig`). The
|
|
9
|
+
* full API key is never returned — only a short, masked prefix.
|
|
10
|
+
*/
|
|
11
|
+
export function describeConfig(args) {
|
|
12
|
+
const store = readStore();
|
|
13
|
+
let url = null;
|
|
14
|
+
let urlSource = "none";
|
|
15
|
+
if (args.flags.get("url")) {
|
|
16
|
+
url = args.flags.get("url");
|
|
17
|
+
urlSource = "flag";
|
|
18
|
+
}
|
|
19
|
+
else if (process.env.PANE_URL) {
|
|
20
|
+
url = process.env.PANE_URL;
|
|
21
|
+
urlSource = "env";
|
|
22
|
+
}
|
|
23
|
+
else if (store.url) {
|
|
24
|
+
url = store.url;
|
|
25
|
+
urlSource = "store";
|
|
26
|
+
}
|
|
27
|
+
let apiKey = null;
|
|
28
|
+
let keySource = "none";
|
|
29
|
+
if (args.flags.get("api-key")) {
|
|
30
|
+
apiKey = args.flags.get("api-key");
|
|
31
|
+
keySource = "flag";
|
|
32
|
+
}
|
|
33
|
+
else if (process.env.PANE_API_KEY) {
|
|
34
|
+
apiKey = process.env.PANE_API_KEY;
|
|
35
|
+
keySource = "env";
|
|
36
|
+
}
|
|
37
|
+
else if (store.apiKey) {
|
|
38
|
+
apiKey = store.apiKey;
|
|
39
|
+
keySource = "store";
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
url: url ? url.replace(/\/$/, "") : null,
|
|
43
|
+
url_source: urlSource,
|
|
44
|
+
key_prefix: apiKey ? apiKey.slice(0, 10) + "…" : null,
|
|
45
|
+
key_source: keySource,
|
|
46
|
+
config_path: storePath(),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The hosted Pane relay. Used as the relay-URL fallback so a fresh user only
|
|
51
|
+
* needs an API key — `pane register` against the hosted relay, then go. A
|
|
52
|
+
* self-hoster overrides it with `--url` / `PANE_URL` / `pane register --url`.
|
|
53
|
+
*/
|
|
54
|
+
export const DEFAULT_RELAY_URL = "https://relay.paneui.com";
|
|
55
|
+
/**
|
|
56
|
+
* Resolve relay URL + API key. Precedence (highest first):
|
|
57
|
+
* url: --url flag → PANE_URL env → store.url → DEFAULT_RELAY_URL
|
|
58
|
+
* apiKey: --api-key → PANE_API_KEY env → store.apiKey
|
|
59
|
+
* The store is written by `pane register`, so later commands need no env vars.
|
|
60
|
+
*/
|
|
61
|
+
export function resolveConfig(args) {
|
|
62
|
+
const store = readStore();
|
|
63
|
+
const url = args.flags.get("url") ??
|
|
64
|
+
process.env.PANE_URL ??
|
|
65
|
+
store.url ??
|
|
66
|
+
DEFAULT_RELAY_URL;
|
|
67
|
+
const apiKey = args.flags.get("api-key") ?? process.env.PANE_API_KEY ?? store.apiKey ?? "";
|
|
68
|
+
if (!apiKey) {
|
|
69
|
+
fail("missing API key: set PANE_API_KEY, pass --api-key <key>, or run 'pane register'", "config_error");
|
|
70
|
+
}
|
|
71
|
+
return { url: url.replace(/\/$/, ""), apiKey };
|
|
72
|
+
}
|
|
73
|
+
/** Build a PaneClient from resolved config. */
|
|
74
|
+
export function makeClient(args) {
|
|
75
|
+
const cfg = resolveConfig(args);
|
|
76
|
+
return new PaneClient({ url: cfg.url, apiKey: cfg.apiKey });
|
|
77
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// pane — command-line client for the Pane relay.
|
|
3
|
+
//
|
|
4
|
+
// Config: PANE_URL and PANE_API_KEY (env), overridable with --url / --api-key.
|
|
5
|
+
// Output is JSON by default. Every command self-documents via --help.
|
|
6
|
+
import { parseArgs, ArgvError } from "./argv.js";
|
|
7
|
+
import { runCreate, createHelp } from "./commands/create.js";
|
|
8
|
+
import { runState, stateHelp } from "./commands/state.js";
|
|
9
|
+
import { runSend, sendHelp } from "./commands/send.js";
|
|
10
|
+
import { runWatch, watchHelp } from "./commands/watch.js";
|
|
11
|
+
import { runRegister, registerHelp } from "./commands/register.js";
|
|
12
|
+
import { runArtifact, artifactHelp } from "./commands/artifact.js";
|
|
13
|
+
import { runConfig, configHelp } from "./commands/config.js";
|
|
14
|
+
import { runLogout, logoutHelp } from "./commands/logout.js";
|
|
15
|
+
import { runKeys, keysHelp } from "./commands/keys.js";
|
|
16
|
+
import { runDelete, deleteHelp } from "./commands/delete.js";
|
|
17
|
+
const VERSION = "0.0.1";
|
|
18
|
+
const ROOT_HELP = `pane — a round-trip UI channel between agents and humans
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
pane <command> [options]
|
|
22
|
+
|
|
23
|
+
Commands:
|
|
24
|
+
register Provision an agent API key (POST /v1/register) and save it
|
|
25
|
+
to the CLI config file. Run this once before other commands.
|
|
26
|
+
create Create a session (POST /v1/sessions). Prints session_id,
|
|
27
|
+
urls, tokens, expires_at.
|
|
28
|
+
artifact Manage reusable, versioned artifacts (create / version /
|
|
29
|
+
update / search / list / show).
|
|
30
|
+
state <id> Non-blocking snapshot: session metadata + event log.
|
|
31
|
+
send <id> Emit an agent event into a session.
|
|
32
|
+
watch <id> Stream a session's events as JSON-lines on stdout
|
|
33
|
+
(long-lived; the building block for pipe-readers).
|
|
34
|
+
delete <id> Close/delete a session (DELETE /v1/sessions/:id).
|
|
35
|
+
keys Inspect or revoke YOUR agent's API key (list / revoke).
|
|
36
|
+
config Show the resolved relay config (no network call).
|
|
37
|
+
logout Clear the locally-saved relay URL + API key.
|
|
38
|
+
|
|
39
|
+
Run \`pane <command> --help\` for command-specific options.
|
|
40
|
+
|
|
41
|
+
Config:
|
|
42
|
+
PANE_URL Relay base URL. Override: --url <url>
|
|
43
|
+
PANE_API_KEY Agent API key. Override: --api-key <key>
|
|
44
|
+
'pane register' provisions the API key and saves it (with the URL) to
|
|
45
|
+
\${XDG_CONFIG_HOME:-~/.config}/pane/config.json — afterwards commands need
|
|
46
|
+
only PANE_URL (or nothing) set.
|
|
47
|
+
|
|
48
|
+
Global flags:
|
|
49
|
+
-h, --help Show help.
|
|
50
|
+
-v, --version Print version.
|
|
51
|
+
|
|
52
|
+
Output: stdout is machine-readable JSON; errors go to stderr as
|
|
53
|
+
{"error":{"code","message"}} with a non-zero exit.`;
|
|
54
|
+
// Flags that never take a value. `json` is kept here purely for forward-compat
|
|
55
|
+
// (JSON is currently the only output mode): accepting `--json` as a no-op bool
|
|
56
|
+
// means a future `--text`/`--json` toggle won't break existing invocations. It
|
|
57
|
+
// is intentionally undocumented in --help.
|
|
58
|
+
//
|
|
59
|
+
// `version` is deliberately NOT here: the top-level `-v` / `--version` is
|
|
60
|
+
// handled from rawArgv[0] before parseArgs runs, so it never needs to be a
|
|
61
|
+
// boolean flag — and keeping it out lets `pane create --version <n>` /
|
|
62
|
+
// `pane artifact version` consume a value as a normal value-flag.
|
|
63
|
+
const BOOLEAN_FLAGS = new Set(["json", "once", "help", "print-key", "yes"]);
|
|
64
|
+
async function main() {
|
|
65
|
+
const rawArgv = process.argv.slice(2);
|
|
66
|
+
// Version: handle before anything else.
|
|
67
|
+
if (rawArgv[0] === "-v" || rawArgv[0] === "--version") {
|
|
68
|
+
process.stdout.write(VERSION + "\n");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const command = rawArgv[0];
|
|
72
|
+
const rest = rawArgv.slice(1);
|
|
73
|
+
if (command === undefined ||
|
|
74
|
+
command === "-h" ||
|
|
75
|
+
command === "--help" ||
|
|
76
|
+
command === "help") {
|
|
77
|
+
process.stdout.write(ROOT_HELP + "\n");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
let args;
|
|
81
|
+
try {
|
|
82
|
+
args = parseArgs(rest, BOOLEAN_FLAGS);
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
if (e instanceof ArgvError) {
|
|
86
|
+
process.stderr.write(JSON.stringify({
|
|
87
|
+
error: { code: "invalid_args", message: e.message },
|
|
88
|
+
}) + "\n");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
throw e;
|
|
92
|
+
}
|
|
93
|
+
const helps = {
|
|
94
|
+
register: registerHelp,
|
|
95
|
+
create: createHelp,
|
|
96
|
+
artifact: artifactHelp,
|
|
97
|
+
state: stateHelp,
|
|
98
|
+
send: sendHelp,
|
|
99
|
+
watch: watchHelp,
|
|
100
|
+
delete: deleteHelp,
|
|
101
|
+
keys: keysHelp,
|
|
102
|
+
config: configHelp,
|
|
103
|
+
logout: logoutHelp,
|
|
104
|
+
};
|
|
105
|
+
if (!(command in helps)) {
|
|
106
|
+
process.stderr.write(JSON.stringify({
|
|
107
|
+
error: {
|
|
108
|
+
code: "unknown_command",
|
|
109
|
+
message: `unknown command '${command}' — run 'pane --help'`,
|
|
110
|
+
},
|
|
111
|
+
}) + "\n");
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
if (args.bools.has("help")) {
|
|
115
|
+
process.stdout.write(helps[command] + "\n");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
switch (command) {
|
|
119
|
+
case "register":
|
|
120
|
+
await runRegister(args);
|
|
121
|
+
break;
|
|
122
|
+
case "create":
|
|
123
|
+
await runCreate(args);
|
|
124
|
+
break;
|
|
125
|
+
case "artifact":
|
|
126
|
+
await runArtifact(args);
|
|
127
|
+
break;
|
|
128
|
+
case "state":
|
|
129
|
+
await runState(args);
|
|
130
|
+
break;
|
|
131
|
+
case "send":
|
|
132
|
+
await runSend(args);
|
|
133
|
+
break;
|
|
134
|
+
case "watch":
|
|
135
|
+
await runWatch(args);
|
|
136
|
+
break;
|
|
137
|
+
case "delete":
|
|
138
|
+
await runDelete(args);
|
|
139
|
+
break;
|
|
140
|
+
case "keys":
|
|
141
|
+
await runKeys(args);
|
|
142
|
+
break;
|
|
143
|
+
case "config":
|
|
144
|
+
await runConfig(args);
|
|
145
|
+
break;
|
|
146
|
+
case "logout":
|
|
147
|
+
await runLogout();
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
main().catch((err) => {
|
|
152
|
+
process.stderr.write(JSON.stringify({
|
|
153
|
+
error: {
|
|
154
|
+
code: "internal",
|
|
155
|
+
message: err instanceof Error ? err.message : String(err),
|
|
156
|
+
},
|
|
157
|
+
}) + "\n");
|
|
158
|
+
process.exit(1);
|
|
159
|
+
});
|
package/dist/input.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Helpers for reading CLI inputs that may be either a file path or an inline
|
|
2
|
+
// literal (JSON, or raw text for an HTML artifact body).
|
|
3
|
+
import { readFileSync, statSync } from "node:fs";
|
|
4
|
+
/**
|
|
5
|
+
* True if `value` names an existing file. Only a missing path (ENOENT) is
|
|
6
|
+
* treated as "not a file" — any other fs error (EACCES, ELOOP, …) propagates
|
|
7
|
+
* with a labeled message rather than being misreported as inline content.
|
|
8
|
+
*/
|
|
9
|
+
function isFilePath(value) {
|
|
10
|
+
try {
|
|
11
|
+
return statSync(value).isFile();
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
if (e &&
|
|
15
|
+
typeof e === "object" &&
|
|
16
|
+
e.code === "ENOENT") {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const code = e && typeof e === "object" ? e.code : undefined;
|
|
20
|
+
throw new Error(`cannot stat '${value}'${code ? ` (${code})` : ""}: ${e instanceof Error ? e.message : String(e)}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve a value that is either a file path or an inline JSON literal.
|
|
25
|
+
* Returns the parsed JSON. Throws on parse failure.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveJson(value, label) {
|
|
28
|
+
const raw = isFilePath(value) ? readFileSync(value, "utf8") : value;
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(raw);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
throw new Error(`${label}: not valid JSON (${e instanceof Error ? e.message : String(e)})`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolve raw text that is either a file path or an inline literal — no JSON
|
|
38
|
+
* parsing. Used for an inline HTML artifact body.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveText(value) {
|
|
41
|
+
return isFilePath(value) ? readFileSync(value, "utf8") : value;
|
|
42
|
+
}
|
package/dist/output.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// stdout/stderr helpers. The CLI is JSON-by-default: machine-readable on
|
|
2
|
+
// stdout, human errors on stderr.
|
|
3
|
+
import { PaneApiError } from "@paneui/core";
|
|
4
|
+
/** Print a value as pretty JSON to stdout. */
|
|
5
|
+
export function printJson(value) {
|
|
6
|
+
process.stdout.write(JSON.stringify(value, null, 2) + "\n");
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Print a single compact JSON line to stdout and flush. Used by `pane watch`
|
|
10
|
+
* so a pipe-reader (e.g. Claude Code's Monitor tool) sees each event
|
|
11
|
+
* immediately, one event per line.
|
|
12
|
+
*/
|
|
13
|
+
export function printJsonLine(value) {
|
|
14
|
+
process.stdout.write(JSON.stringify(value) + "\n");
|
|
15
|
+
}
|
|
16
|
+
/** Print an error envelope to stderr and exit non-zero. */
|
|
17
|
+
export function fail(message, code = "error", details, extra) {
|
|
18
|
+
const error = { code, message };
|
|
19
|
+
if (extra?.hint !== undefined)
|
|
20
|
+
error["hint"] = extra.hint;
|
|
21
|
+
if (extra?.retryable !== undefined)
|
|
22
|
+
error["retryable"] = extra.retryable;
|
|
23
|
+
if (extra?.docs_url !== undefined)
|
|
24
|
+
error["docs_url"] = extra.docs_url;
|
|
25
|
+
if (details !== undefined)
|
|
26
|
+
error["details"] = details;
|
|
27
|
+
process.stderr.write(JSON.stringify({ error }) + "\n");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
/** Translate a thrown error (incl. PaneApiError) into a fail() exit. */
|
|
31
|
+
export function failFromError(err) {
|
|
32
|
+
if (err instanceof PaneApiError) {
|
|
33
|
+
fail(err.message, err.code, err.details, {
|
|
34
|
+
hint: err.hint,
|
|
35
|
+
retryable: err.retryable,
|
|
36
|
+
docs_url: err.docsUrl,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
fail(err instanceof Error ? err.message : String(err), "internal");
|
|
40
|
+
}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Persisted CLI config: ${XDG_CONFIG_HOME or ~/.config}/pane/config.json.
|
|
2
|
+
//
|
|
3
|
+
// Holds the relay URL and the agent API key obtained via `pane register`, so
|
|
4
|
+
// later commands need no env vars. The file holds a secret — it is written
|
|
5
|
+
// 0600. Tiny and synchronous; no deps.
|
|
6
|
+
import { readFileSync, writeFileSync, mkdirSync, chmodSync, rmSync, } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join, dirname } from "node:path";
|
|
9
|
+
/** Absolute path to the config file (honours XDG_CONFIG_HOME). */
|
|
10
|
+
export function storePath() {
|
|
11
|
+
const base = process.env.XDG_CONFIG_HOME && process.env.XDG_CONFIG_HOME.trim() !== ""
|
|
12
|
+
? process.env.XDG_CONFIG_HOME
|
|
13
|
+
: join(homedir(), ".config");
|
|
14
|
+
return join(base, "pane", "config.json");
|
|
15
|
+
}
|
|
16
|
+
/** Read the persisted config. Returns {} if the file is missing or unparseable. */
|
|
17
|
+
export function readStore() {
|
|
18
|
+
let text;
|
|
19
|
+
try {
|
|
20
|
+
text = readFileSync(storePath(), "utf8");
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const parsed = JSON.parse(text);
|
|
27
|
+
if (parsed === null ||
|
|
28
|
+
typeof parsed !== "object" ||
|
|
29
|
+
Array.isArray(parsed)) {
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
const out = {};
|
|
33
|
+
if (typeof parsed.url === "string")
|
|
34
|
+
out.url = parsed.url;
|
|
35
|
+
if (typeof parsed.apiKey === "string")
|
|
36
|
+
out.apiKey = parsed.apiKey;
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Merge `patch` into the existing config and write it back as pretty JSON.
|
|
45
|
+
* Creates the parent directory if needed; the file is written with mode 0600.
|
|
46
|
+
*/
|
|
47
|
+
export function writeStore(patch) {
|
|
48
|
+
const path = storePath();
|
|
49
|
+
const merged = { ...readStore(), ...patch };
|
|
50
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
51
|
+
writeFileSync(path, JSON.stringify(merged, null, 2) + "\n", { mode: 0o600 });
|
|
52
|
+
// Ensure mode even if the file pre-existed with looser permissions.
|
|
53
|
+
chmodSync(path, 0o600);
|
|
54
|
+
return path;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Delete the persisted config file (URL + API key). Idempotent — no error if
|
|
58
|
+
* the file never existed. Returns the path it targeted. Used by `pane logout`.
|
|
59
|
+
*/
|
|
60
|
+
export function clearStore() {
|
|
61
|
+
const path = storePath();
|
|
62
|
+
rmSync(path, { force: true });
|
|
63
|
+
return path;
|
|
64
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@paneui/cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Command-line client for the Pane relay: create sessions, inspect state, send and watch events.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"pane",
|
|
9
|
+
"cli",
|
|
10
|
+
"agent",
|
|
11
|
+
"relay",
|
|
12
|
+
"human-in-the-loop"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/aerolalit/paneui#readme",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/aerolalit/paneui.git",
|
|
18
|
+
"directory": "packages/cli"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/aerolalit/paneui/issues"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"bin": {
|
|
30
|
+
"pane": "dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"LICENSE",
|
|
35
|
+
"README.md"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"test:unit": "vitest run"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@paneui/core": "^0.0.1"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^22.7.0",
|
|
48
|
+
"typescript": "^5.6.0",
|
|
49
|
+
"vitest": "^4.1.6"
|
|
50
|
+
}
|
|
51
|
+
}
|