@multiplayer-app/cli 2.0.0 → 2.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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -30
  3. package/dist/index.js +83 -22
  4. package/package.json +4 -2
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Multiplayer Software, Inc.
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,77 +1,104 @@
1
1
  # @multiplayer-app/debugging-agent
2
2
 
3
- Multiplayer CLI — AI-powered debugging agent and release management tool.
3
+ AI-powered debugging agent and release management CLI for [Multiplayer](https://multiplayer.app).
4
4
 
5
5
  ## Install
6
6
 
7
- ```
7
+ ```sh
8
8
  npm install -g @multiplayer-app/debugging-agent
9
9
  ```
10
10
 
11
- ## Usage
11
+ ## Commands
12
12
 
13
- ```
14
- multiplayer [command] [options]
15
- ```
16
-
17
- If no command is provided, the debugging agent starts in TUI mode.
13
+ | Command | Description |
14
+ |---------|-------------|
15
+ | `multiplayer` | Start the debugging agent (TUI by default) |
16
+ | `multiplayer releases create` | Register a release |
17
+ | `multiplayer deployments create` | Register a deployment |
18
+ | `multiplayer sourcemaps upload` | Upload sourcemap files |
18
19
 
19
20
  ---
20
21
 
21
22
  ## Debugging Agent
22
23
 
23
- Automatically resolves issues using AI. Connects to the Multiplayer backend and processes incoming issues in real time.
24
+ Connects to the Multiplayer backend and automatically resolves incoming issues using AI.
24
25
 
25
- ```
26
+ ```sh
26
27
  multiplayer [options]
27
28
  ```
28
29
 
29
- Options can be provided as flags, environment variables, or a `.multiplayer/config` profile file.
30
+ Options are resolved in this order: **CLI flag → environment variable config profile**.
30
31
 
31
32
  | Flag | Env var | Description |
32
33
  |------|---------|-------------|
33
34
  | `--api-key <key>` | `MULTIPLAYER_API_KEY` | Multiplayer API key |
34
35
  | `--dir <path>` | `MULTIPLAYER_DIR` | Project directory (must be a git repo) |
35
36
  | `--model <name>` | `AI_MODEL` | AI model (e.g. `claude-sonnet-4-6`, `gpt-4o`) |
36
- | `--model-key <key>` | `AI_API_KEY` | API key for the AI provider (not needed for Claude) |
37
- | `--model-url <url>` | `AI_BASE_URL` | Optional base URL for OpenAI-compatible APIs |
38
- | `--headless` | `MULTIPLAYER_HEADLESS=true` | Run without TUI, outputs structured JSON logs |
39
- | `--profile <name>` | `MULTIPLAYER_PROFILE` | Config profile to use (default: `default`) |
37
+ | `--model-key <key>` | `AI_API_KEY` | AI provider API key (not required for Claude models) |
38
+ | `--model-url <url>` | `AI_BASE_URL` | Base URL for OpenAI-compatible APIs |
39
+ | `--headless` | `MULTIPLAYER_HEADLESS=true` | Run without TUI outputs structured JSON logs |
40
+ | `--profile <name>` | `MULTIPLAYER_PROFILE` | Config profile to load (default: `default`) |
40
41
  | `--name <name>` | `MULTIPLAYER_AGENT_NAME` | Agent name (defaults to hostname) |
41
- | `--max-concurrent <n>` | `MULTIPLAYER_MAX_CONCURRENT` | Max parallel issues (default: `2`) |
42
+ | `--max-concurrent <n>` | `MULTIPLAYER_MAX_CONCURRENT` | Max issues resolved in parallel (default: `2`) |
42
43
  | `--no-git-branch` | `MULTIPLAYER_NO_GIT_BRANCH=true` | Work in current branch — no worktree, no push |
43
- | `--health-port <port>` | `MULTIPLAYER_HEALTH_PORT` | HTTP health check port (headless only) |
44
+ | `--health-port <port>` | `MULTIPLAYER_HEALTH_PORT` | HTTP health check port (headless mode only) |
44
45
  | `--url <url>` | `MULTIPLAYER_URL` | Multiplayer API base URL |
45
46
 
46
47
  ### TUI mode (default)
47
48
 
48
- ```
49
+ An interactive terminal dashboard that shows active sessions and live logs.
50
+
51
+ ```sh
49
52
  multiplayer --api-key <key> --dir /path/to/repo --model claude-sonnet-4-6
50
53
  ```
51
54
 
52
55
  ### Headless mode
53
56
 
54
- ```
57
+ Outputs newline-delimited JSON logs — suitable for CI, containers, and log aggregators.
58
+
59
+ ```sh
55
60
  multiplayer --headless --api-key <key> --dir /path/to/repo --model claude-sonnet-4-6
56
61
  ```
57
62
 
63
+ In headless mode, `SIGTERM` waits for active sessions to finish before exiting; `SIGINT` exits immediately.
64
+
58
65
  ### Config profiles
59
66
 
60
- Create a `.multiplayer/config` file in your project or home directory:
67
+ Create a `.multiplayer/config` file in your project directory or home directory (`~/.multiplayer/config`). Uses INI format — the same as AWS credentials.
61
68
 
62
69
  ```ini
63
70
  [default]
64
- api_key = <your-api-key>
65
- dir = /path/to/repo
66
- model = claude-sonnet-4-6
71
+ api_key = <your-api-key>
72
+ dir = /path/to/repo
73
+ model = claude-sonnet-4-6
67
74
  max_concurrent = 2
75
+
76
+ [staging]
77
+ api_key = <staging-api-key>
78
+ dir = /path/to/staging-repo
79
+ model = gpt-4o
80
+ model_key = <openai-api-key>
68
81
  ```
69
82
 
83
+ All supported profile keys:
84
+
85
+ | Key | Description |
86
+ |-----|-------------|
87
+ | `api_key` | Multiplayer API key |
88
+ | `dir` | Project directory |
89
+ | `model` | AI model name |
90
+ | `model_key` | AI provider API key |
91
+ | `model_url` | Base URL for OpenAI-compatible APIs |
92
+ | `name` | Agent name |
93
+ | `url` | Multiplayer API base URL |
94
+ | `max_concurrent` | Max parallel issues |
95
+ | `no_git_branch` | `true` to skip branch/worktree creation |
96
+
70
97
  ---
71
98
 
72
99
  ## Releases
73
100
 
74
- ```
101
+ ```sh
75
102
  multiplayer releases create [options]
76
103
  ```
77
104
 
@@ -87,7 +114,7 @@ multiplayer releases create [options]
87
114
 
88
115
  **Example:**
89
116
 
90
- ```
117
+ ```sh
91
118
  multiplayer releases create \
92
119
  --api-key $MULTIPLAYER_API_KEY \
93
120
  --service my-service \
@@ -100,7 +127,7 @@ multiplayer releases create \
100
127
 
101
128
  ## Deployments
102
129
 
103
- ```
130
+ ```sh
104
131
  multiplayer deployments create [options]
105
132
  ```
106
133
 
@@ -108,13 +135,13 @@ multiplayer deployments create [options]
108
135
  |------|---------|-------------|
109
136
  | `--api-key <key>` | `MULTIPLAYER_API_KEY` | Multiplayer API key |
110
137
  | `--service <name>` | `SERVICE_NAME` | Service name |
111
- | `--release <version>` | `RELEASE` | Release version |
138
+ | `--release <version>` | `VERSION` | Release version |
112
139
  | `--environment <name>` | `ENVIRONMENT` | Environment name |
113
140
  | `--base-url <url>` | `BASE_URL` | API base URL (optional) |
114
141
 
115
142
  **Example:**
116
143
 
117
- ```
144
+ ```sh
118
145
  multiplayer deployments create \
119
146
  --api-key $MULTIPLAYER_API_KEY \
120
147
  --service my-service \
@@ -126,7 +153,7 @@ multiplayer deployments create \
126
153
 
127
154
  ## Sourcemaps
128
155
 
129
- ```
156
+ ```sh
130
157
  multiplayer sourcemaps upload <directories...> [options]
131
158
  ```
132
159
 
@@ -139,7 +166,7 @@ multiplayer sourcemaps upload <directories...> [options]
139
166
 
140
167
  **Example:**
141
168
 
142
- ```
169
+ ```sh
143
170
  multiplayer sourcemaps upload ./dist ./build \
144
171
  --api-key $MULTIPLAYER_API_KEY \
145
172
  --service my-service \
package/dist/index.js CHANGED
@@ -148640,6 +148640,21 @@ var decodeApiKeyPayload = (apiKey) => {
148640
148640
  return {};
148641
148641
  }
148642
148642
  };
148643
+ var validateApiKey = async (url2, apiKey) => {
148644
+ const payload = decodeApiKeyPayload(apiKey);
148645
+ if (!payload.workspace || !payload.project || !payload.integration) {
148646
+ throw new Error("Invalid API key");
148647
+ }
148648
+ const api = createApiService({ url: url2, apiKey });
148649
+ const project = await api.fetchProject(payload.workspace, payload.project);
148650
+ if (!project) {
148651
+ throw new Error("API key validation failed: workspace or project not found");
148652
+ }
148653
+ return {
148654
+ workspace: payload.workspace,
148655
+ project: payload.project
148656
+ };
148657
+ };
148643
148658
 
148644
148659
  // src/components/startup/ApiKeyStep.tsx
148645
148660
  function ApiKeyStep({ config, onComplete }) {
@@ -174950,7 +174965,7 @@ function RateLimitsStep({ config, onComplete }) {
174950
174965
  // src/components/startup/ConnectingStep.tsx
174951
174966
  var import_react29 = __toESM(require_react(), 1);
174952
174967
  function ConnectingStep({ config, onComplete, onBack }) {
174953
- const [status, setStatus] = import_react29.useState("checking-git");
174968
+ const [status, setStatus] = import_react29.useState("checking-api-key");
174954
174969
  const [error3, setError] = import_react29.useState(null);
174955
174970
  const [runId, setRunId] = import_react29.useState(0);
174956
174971
  useKeyboard(({ name }) => {
@@ -174958,7 +174973,7 @@ function ConnectingStep({ config, onComplete, onBack }) {
174958
174973
  onBack?.();
174959
174974
  if (name === "return" && status === "error") {
174960
174975
  setError(null);
174961
- setStatus("checking-git");
174976
+ setStatus("checking-api-key");
174962
174977
  setRunId((v2) => v2 + 1);
174963
174978
  }
174964
174979
  });
@@ -174966,6 +174981,10 @@ function ConnectingStep({ config, onComplete, onBack }) {
174966
174981
  let cancelled = false;
174967
174982
  const run = async () => {
174968
174983
  try {
174984
+ if (cancelled)
174985
+ return;
174986
+ setStatus("checking-api-key");
174987
+ await validateApiKey(config.url, config.apiKey);
174969
174988
  if (cancelled)
174970
174989
  return;
174971
174990
  setStatus("checking-git");
@@ -175000,8 +175019,12 @@ function ConnectingStep({ config, onComplete, onBack }) {
175000
175019
  cancelled = true;
175001
175020
  };
175002
175021
  }, [runId]);
175003
- const gitColor = status === "checking-git" ? "#f59e0b" : status === "error" && error3?.includes("git") ? "#ef4444" : "#10b981";
175004
- const gitSymbol = status === "checking-git" ? "\u25CC" : status === "error" && error3?.includes("git") ? "\u2715" : "\u2713";
175022
+ const apiKeyPassed = status !== "checking-api-key" && !(status === "error" && !error3?.includes("git") && !error3?.includes("AI") && !error3?.includes("model"));
175023
+ const apiKeyColor = status === "checking-api-key" ? "#f59e0b" : status === "error" && !apiKeyPassed ? "#ef4444" : "#10b981";
175024
+ const apiKeySymbol = status === "checking-api-key" ? "\u25CC" : status === "error" && !apiKeyPassed ? "\u2715" : "\u2713";
175025
+ const gitStarted = status !== "checking-api-key" && status !== "error" || status === "error" && (error3?.includes("git") || apiKeyPassed);
175026
+ const gitColor = !gitStarted ? "#6b7280" : status === "checking-git" ? "#f59e0b" : status === "error" && error3?.includes("git") ? "#ef4444" : "#10b981";
175027
+ const gitSymbol = !gitStarted ? "\xB7" : status === "checking-git" ? "\u25CC" : status === "error" && error3?.includes("git") ? "\u2715" : "\u2713";
175005
175028
  return /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
175006
175029
  flexDirection: "column",
175007
175030
  gap: 1,
@@ -175015,6 +175038,22 @@ function ConnectingStep({ config, onComplete, onBack }) {
175015
175038
  marginTop: 1,
175016
175039
  gap: 0,
175017
175040
  children: [
175041
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
175042
+ gap: 2,
175043
+ children: [
175044
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
175045
+ fg: apiKeyColor,
175046
+ children: apiKeySymbol
175047
+ }, undefined, false, undefined, this),
175048
+ /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
175049
+ children: "API key"
175050
+ }, undefined, false, undefined, this),
175051
+ status === "checking-api-key" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
175052
+ attributes: tuiAttrs({ dim: true }),
175053
+ children: "validating..."
175054
+ }, undefined, false, undefined, this)
175055
+ ]
175056
+ }, undefined, true, undefined, this),
175018
175057
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
175019
175058
  gap: 2,
175020
175059
  children: [
@@ -175031,7 +175070,7 @@ function ConnectingStep({ config, onComplete, onBack }) {
175031
175070
  }, undefined, false, undefined, this)
175032
175071
  ]
175033
175072
  }, undefined, true, undefined, this),
175034
- status !== "checking-git" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
175073
+ status !== "checking-api-key" && status !== "checking-git" && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("box", {
175035
175074
  gap: 2,
175036
175075
  children: [
175037
175076
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
@@ -176665,9 +176704,22 @@ function iniToProfileConfig(raw) {
176665
176704
  return cfg;
176666
176705
  }
176667
176706
  function findConfigFile(projectDir) {
176707
+ function ancestorCandidates(dir) {
176708
+ const result = [];
176709
+ let current = path12.resolve(dir);
176710
+ const home = os4.homedir();
176711
+ while (true) {
176712
+ result.push(path12.join(current, ".multiplayer", "config"));
176713
+ const parent = path12.dirname(current);
176714
+ if (parent === current || current === home)
176715
+ break;
176716
+ current = parent;
176717
+ }
176718
+ return result;
176719
+ }
176668
176720
  const candidates = [
176669
176721
  projectDir ? path12.join(projectDir, ".multiplayer", "config") : undefined,
176670
- path12.join(process.cwd(), ".multiplayer", "config"),
176722
+ ...ancestorCandidates(process.cwd()),
176671
176723
  path12.join(os4.homedir(), ".multiplayer", "config")
176672
176724
  ].filter(Boolean);
176673
176725
  const seen = new Set;
@@ -176701,7 +176753,7 @@ function loadProfile(profileName, projectDir) {
176701
176753
  // src/cli/flags.ts
176702
176754
  function parseFlags(argv) {
176703
176755
  const program2 = new Command;
176704
- program2.name("multiplayer").description("Multiplayer debugging agent \u2014 automatically resolves issues using AI").version("0.0.1").option("--headless", "Run without TUI (structured log output, requires full config); also set via MULTIPLAYER_HEADLESS=true").option("--profile <name>", 'Config profile to use from .multiplayer/config (default: "default"); also set via MULTIPLAYER_PROFILE').option("--url <url>", "Multiplayer base API URL", API_URL).option("--api-key <key>", "Multiplayer API key").option("--name <name>", "Agent name (defaults to hostname)").option("--dir <path>", "Project directory (must be a git repo)").option("--model <name>", "AI model name (e.g. claude-sonnet-4-6, gpt-4o)").option("--model-key <key>", "API key for the AI provider").option("--model-url <url>", "Optional base URL for OpenAI-compatible APIs").option("--max-concurrent <n>", "Maximum number of issues to resolve in parallel", String(DEFAULT_MAX_CONCURRENT)).option("--no-git-branch", "Work in current branch \u2014 no worktree, no new branch, no push").option("--health-port <port>", "Port for HTTP health check endpoint (headless mode only); also set via MULTIPLAYER_HEALTH_PORT").exitOverride();
176756
+ program2.name("multiplayer").description("Multiplayer debugging agent \u2014 automatically resolves issues using AI").version("0.0.1").option("--headless", "Run without TUI (structured log output, requires full config); also set via MULTIPLAYER_HEADLESS=true").option("--profile <name>", 'Config profile to use from .multiplayer/config (default: "default"); also set via MULTIPLAYER_PROFILE').option("--url <url>", "Multiplayer base API URL").option("--api-key <key>", "Multiplayer API key").option("--name <name>", "Agent name (defaults to hostname)").option("--dir <path>", "Project directory (must be a git repo)").option("--model <name>", "AI model name (e.g. claude-sonnet-4-6, gpt-4o)").option("--model-key <key>", "API key for the AI provider").option("--model-url <url>", "Optional base URL for OpenAI-compatible APIs").option("--max-concurrent <n>", "Maximum number of issues to resolve in parallel", String(DEFAULT_MAX_CONCURRENT)).option("--no-git-branch", "Work in current branch \u2014 no worktree, no new branch, no push").option("--health-port <port>", "Port for HTTP health check endpoint (headless mode only); also set via MULTIPLAYER_HEALTH_PORT").exitOverride();
176705
176757
  try {
176706
176758
  program2.parse(argv);
176707
176759
  } catch (err) {
@@ -177027,21 +177079,30 @@ if (firstArg && CLI_SUBCOMMANDS.has(firstArg)) {
177027
177079
  `);
177028
177080
  };
177029
177081
  logger2("info", `Using profile: ${profileName}`);
177030
- const controller = new RuntimeController(config, logger2);
177031
- if (healthPort) {
177032
- const healthServer = startHealthServer(healthPort, controller);
177033
- logger2("info", `Health server listening on port ${healthPort}`);
177034
- controller.on("quit", () => healthServer.close());
177035
- }
177036
- controller.on("quit", () => {
177037
- process.exit(0);
177038
- });
177039
- process.on("SIGTERM", () => {
177040
- logger2("info", "SIGTERM received \u2014 waiting for active sessions to complete");
177041
- controller.quit("after-current");
177042
- });
177043
- process.on("SIGINT", () => controller.quit("now"));
177044
- controller.connect();
177082
+ (async () => {
177083
+ try {
177084
+ await validateApiKey(config.url, config.apiKey);
177085
+ } catch (err) {
177086
+ process.stderr.write(`API key validation failed: ${err.message}
177087
+ `);
177088
+ process.exit(1);
177089
+ }
177090
+ const controller = new RuntimeController(config, logger2);
177091
+ if (healthPort) {
177092
+ const healthServer = startHealthServer(healthPort, controller);
177093
+ logger2("info", `Health server listening on port ${healthPort}`);
177094
+ controller.on("quit", () => healthServer.close());
177095
+ }
177096
+ controller.on("quit", () => {
177097
+ process.exit(0);
177098
+ });
177099
+ process.on("SIGTERM", () => {
177100
+ logger2("info", "SIGTERM received \u2014 waiting for active sessions to complete");
177101
+ controller.quit("after-current");
177102
+ });
177103
+ process.on("SIGINT", () => controller.quit("now"));
177104
+ controller.connect();
177105
+ })();
177045
177106
  } else {
177046
177107
  (async () => {
177047
177108
  const renderer = await createCliRenderer({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplayer-app/cli",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Multiplayer CLI",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -11,8 +11,10 @@
11
11
  "start": "bun dist/index.js",
12
12
  "dev": "bun src/index.tsx",
13
13
  "typecheck": "tsc --noEmit",
14
- "lint": "eslint src/**/*.ts --config eslint.config.js"
14
+ "lint": "eslint src/**/*.ts --config eslint.config.js",
15
+ "prepublish": "npm run build"
15
16
  },
17
+ "license": "MIT",
16
18
  "dependencies": {
17
19
  "@anthropic-ai/claude-agent-sdk": "0.2.73",
18
20
  "@anthropic-ai/sdk": "^0.78.0",