@retasc/cli 1.1.1 → 1.1.3

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.
@@ -58,52 +58,75 @@ export function mcpMarkerEntry(workspaceId) {
58
58
  export function mcpMarkerConfigBlock(workspaceId) {
59
59
  return JSON.stringify({ mcpServers: { [SERVER_NAME]: mcpMarkerEntry(workspaceId) } }, null, 2);
60
60
  }
61
- /** Try `claude mcp add`. Returns true on success, false if claude is absent or errored. */
62
- function tryClaudeCli(url, key, scope) {
63
- const args = [
61
+ // --- `claude mcp add` argv builders (pure, exported for tests) -------------
62
+ // RTSC-99: the stdio builders put SERVER_NAME *before* the variadic `--env`
63
+ // flags. `--env` consumes every following non-`--` token, so a server name after
64
+ // it was eaten as a bogus env var ("Invalid environment variable format: retasc").
65
+ // Name-first + a closing `--` keeps env values bounded and the command intact.
66
+ /** Build `claude mcp add` argv for the HTTP transport. (`url` is a positional
67
+ * BEFORE the variadic `--header`, so the header flag can't swallow it.) */
68
+ export function buildHttpAddArgs(url, key, scope) {
69
+ return [
64
70
  "mcp", "add",
65
71
  "--transport", "http",
66
72
  "--scope", scope,
67
73
  SERVER_NAME, url,
68
74
  "--header", `Authorization: Bearer ${key}`,
69
75
  ];
70
- return runClaudeAdd(args);
71
76
  }
72
- /** Try `claude mcp add` for the stdio watchdog proxy (RTSC-44). */
73
- function tryClaudeCliWatchdog(url, key, scope) {
74
- const args = [
77
+ /** Build argv for the stdio watchdog proxy (RTSC-44). */
78
+ export function buildWatchdogAddArgs(url, key, scope) {
79
+ return [
75
80
  "mcp", "add",
81
+ SERVER_NAME,
76
82
  "--transport", "stdio",
77
83
  "--scope", scope,
78
84
  "--env", `RETASC_MCP_URL=${url}`,
79
85
  "--env", `RETASC_MCP_KEY=${key}`,
80
- SERVER_NAME, "--", "retasc", "mcp-proxy",
86
+ "--", "retasc", "mcp-proxy",
81
87
  ];
82
- return runClaudeAdd(args);
83
88
  }
84
- /** Try `claude mcp add` for the secret-free stdio marker (RTSC-92). */
85
- function tryClaudeCliMarker(workspaceId, scope) {
86
- const args = [
89
+ /** Build argv for the secret-free stdio marker (RTSC-92). */
90
+ export function buildMarkerAddArgs(workspaceId, scope) {
91
+ return [
87
92
  "mcp", "add",
93
+ SERVER_NAME,
88
94
  "--transport", "stdio",
89
95
  "--scope", scope,
90
96
  "--env", `RETASC_WORKSPACE=${workspaceId}`,
91
- SERVER_NAME, "--", "retasc", "mcp-proxy",
97
+ "--", "retasc", "mcp-proxy",
92
98
  ];
93
- return runClaudeAdd(args);
99
+ }
100
+ function tryClaudeCli(url, key, scope) {
101
+ return runClaudeAdd(buildHttpAddArgs(url, key, scope));
102
+ }
103
+ function tryClaudeCliWatchdog(url, key, scope) {
104
+ return runClaudeAdd(buildWatchdogAddArgs(url, key, scope));
105
+ }
106
+ function tryClaudeCliMarker(workspaceId, scope) {
107
+ return runClaudeAdd(buildMarkerAddArgs(workspaceId, scope));
94
108
  }
95
109
  function runClaudeAdd(args) {
96
110
  const r = spawnSync("claude", args, { encoding: "utf8" });
97
- if (r.error)
98
- return false; // ENOENT claude not installed
111
+ if (r.error) {
112
+ // ENOENT = `claude` not on PATH (genuinely absent). Any other spawn error is
113
+ // a real failure of a present CLI — report it as such, not as "not detected".
114
+ const absent = r.error.code === "ENOENT";
115
+ return { ok: false, absent, msg: absent ? undefined : r.error.message };
116
+ }
99
117
  if (r.status !== 0) {
100
- // Surface why it failed but don't throw we fall back to the file.
101
- const msg = (r.stderr || r.stdout || "").trim();
102
- if (msg)
103
- console.error(` (claude mcp add: ${msg.split("\n")[0]})`);
104
- return false;
118
+ // CLI ran but rejected the commandpresent, not absent. Surface the real reason.
119
+ const msg = (r.stderr || r.stdout || "").trim().split("\n")[0] || `exited ${r.status}`;
120
+ return { ok: false, absent: false, msg };
105
121
  }
106
- return true;
122
+ return { ok: true };
123
+ }
124
+ /** The fallback note printed after writing .mcp.json — names the REAL reason the
125
+ * CLI path didn't take, never falsely claiming the CLI is missing (RTSC-99). */
126
+ export function fallbackNote(res) {
127
+ return res.absent
128
+ ? "(Claude Code CLI not detected — wrote .mcp.json instead.)"
129
+ : `(claude mcp add failed${res.msg ? `: ${res.msg}` : ""} — wrote .mcp.json instead.)`;
107
130
  }
108
131
  /** Merge a given server entry into ./.mcp.json (project scope on disk). */
109
132
  function writeProjectMcpJson(entry) {
@@ -131,28 +154,28 @@ export function installMcp(opts) {
131
154
  const scope = opts.scope ?? "local";
132
155
  // Watchdog mode (RTSC-44): wire the stdio proxy so claims stay alive automatically.
133
156
  if (opts.watchdog) {
134
- const viaClaude = tryClaudeCliWatchdog(opts.url, opts.key, scope);
135
- if (viaClaude) {
157
+ const res = tryClaudeCliWatchdog(opts.url, opts.key, scope);
158
+ if (res.ok) {
136
159
  console.log(`✓ Registered Retasc with the liveness watchdog (stdio proxy, scope: ${scope}).`);
137
160
  }
138
161
  else {
139
162
  const path = writeProjectMcpJson(mcpProxyEntry(opts.url, opts.key));
140
163
  console.log(`✓ Wrote watchdog MCP config to ${path}`);
141
- console.log(" (Claude Code CLI not detected or unavailable — used .mcp.json instead.)");
164
+ console.log(` ${fallbackNote(res)}`);
142
165
  }
143
166
  console.log("\nWatchdog MCP config (Codex / OpenCode / any stdio MCP client):\n");
144
167
  console.log(mcpProxyConfigBlock(opts.url, opts.key));
145
168
  console.log("\nThe watchdog keeps your claims alive automatically — no per-claim heartbeats. If it ever isn't running you just fall back to the normal lease timeout (safe).");
146
169
  return;
147
170
  }
148
- const viaClaude = tryClaudeCli(opts.url, opts.key, scope);
149
- if (viaClaude) {
171
+ const res = tryClaudeCli(opts.url, opts.key, scope);
172
+ if (res.ok) {
150
173
  console.log(`✓ Registered MCP server "${SERVER_NAME}" with Claude Code (scope: ${scope}).`);
151
174
  }
152
175
  else {
153
176
  const path = writeProjectMcpJson(mcpServerEntry(opts.url, opts.key));
154
177
  console.log(`✓ Wrote MCP config to ${path}`);
155
- console.log(" (Claude Code CLI not detected or unavailable — used .mcp.json instead.)");
178
+ console.log(` ${fallbackNote(res)}`);
156
179
  }
157
180
  console.log("\nMCP config block (for any other agent/client):\n");
158
181
  console.log(mcpConfigBlock(opts.url, opts.key));
@@ -165,14 +188,14 @@ export function installMcp(opts) {
165
188
  */
166
189
  export function installMarker(opts) {
167
190
  const scope = opts.scope ?? "local";
168
- const viaClaude = tryClaudeCliMarker(opts.workspaceId, scope);
169
- if (viaClaude) {
191
+ const res = tryClaudeCliMarker(opts.workspaceId, scope);
192
+ if (res.ok) {
170
193
  console.log(`✓ Registered Retasc watchdog (secret-free marker, scope: ${scope}).`);
171
194
  }
172
195
  else {
173
196
  const path = writeProjectMcpJson(mcpMarkerEntry(opts.workspaceId));
174
197
  console.log(`✓ Wrote secret-free watchdog marker to ${path}`);
175
- console.log(" (Claude Code CLI not detected — used .mcp.json instead.)");
198
+ console.log(` ${fallbackNote(res)}`);
176
199
  }
177
200
  console.log("\nMarker block (any stdio MCP client) — no secret, safe to commit:\n");
178
201
  console.log(mcpMarkerConfigBlock(opts.workspaceId));
package/dist/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
+ import { readFileSync } from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+ import { dirname, join } from "node:path";
3
6
  import { loadConfig, patchConfig, saveConfig, configPath, isLoggedIn } from "./config.js";
4
7
  import { installMcp, normalizeScope } from "./commands/mcp.js";
5
8
  import { installGate } from "./commands/gate.js";
@@ -11,11 +14,16 @@ import { tidyAction, doneAction } from "./commands/tidy.js";
11
14
  import { runProxy } from "./proxy.js";
12
15
  import { deviceLogin } from "./auth.js";
13
16
  import { api } from "./api.js";
17
+ // Single source of truth for the version: read package.json at runtime from the
18
+ // compiled file's location (dist/index.js -> ../package.json). A JSON import won't
19
+ // work here — tsconfig has rootDir "src", so importing ../package.json is outside
20
+ // rootDir and fails tsc.
21
+ const pkg = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf8"));
14
22
  const program = new Command();
15
23
  program
16
24
  .name("retasc")
17
25
  .description("Retasc — sign in, create projects, mint agent API keys, and wire your agent to the MCP server.")
18
- .version("1.1.1");
26
+ .version(pkg.version);
19
27
  function requireLogin() {
20
28
  if (!isLoggedIn()) {
21
29
  console.error("Not signed in. Run `retasc login` first.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retasc/cli",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Retasc CLI — sign in with GitHub, create projects, mint agent API keys, and wire your agent to the Retasc MCP server in one command.",
5
5
  "type": "module",
6
6
  "bin": {