relayrun 0.1.0 → 0.1.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/README.md CHANGED
@@ -48,6 +48,7 @@ rather than wedging the session.
48
48
  |---|---|
49
49
  | `--repo <path>` | Repository to work in (default: current directory) |
50
50
  | `--api-key <key>` | Bill runs to this key instead of your Claude Code login |
51
+ | `--model <id>` | Model to run (default: `claude-sonnet-5`) |
51
52
  | `--mock` | Scripted offline agent — see below |
52
53
  | `--session <id>` | Reattach to an existing session (needs `--token`) |
53
54
  | `--token <tok>` | Runner token for `--session` |
@@ -92,7 +93,12 @@ relayrun --session 8M2zrrx9Ng --token <runner-token>
92
93
 
93
94
  - Node.js >= 20.9.0
94
95
  - `git`, and a repository to run in
95
- - A Claude Code login or an Anthropic API key (unless using `--mock`)
96
+ - **A Claude Code login or an Anthropic API key** (unless using `--mock`)
97
+
98
+ A claude.ai browser session or the Claude Desktop app will *not* work — those
99
+ are separate logins from Claude Code. If no credential is found, `relayrun`
100
+ says so and exits before opening a session, rather than failing later in front
101
+ of whoever you invited.
96
102
 
97
103
  ## License
98
104
 
package/dist/agent/run.js CHANGED
@@ -4,9 +4,8 @@ import { detailForCall } from "./diff.js";
4
4
  import { resultText, summarizeToolCall, summarizeToolResult } from "./summarize.js";
5
5
  import { orientation } from "./orient.js";
6
6
  export async function runRealAgent(opts) {
7
- const { emit, instruction, workingDir, signal, resume, apiKeyHelper } = opts;
7
+ const { emit, instruction, workingDir, signal, resume, apiKeyHelper, model } = opts;
8
8
  const pending = new Map();
9
- const model = process.env.RELAY_MODEL ?? "claude-haiku-4-5";
10
9
  // The SDK wants an AbortController, but ownership of stopping a run belongs
11
10
  // to the runner, which already holds one. Bridge the two.
12
11
  const abort = new AbortController();
package/dist/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { execFile } from "node:child_process";
3
3
  import path from "node:path";
4
4
  import { promisify } from "node:util";
5
+ import { findClaudeLogin, hasClaudeCli, noLoginGuidance } from "./login.js";
5
6
  import { createRepo } from "./repo.js";
6
7
  import { startRunner } from "./runner.js";
7
8
  import { apiKeyHelperPath, keyHint, looksLikeApiKey, SESSION_KEY_ENV, verifyApiKey, } from "./sessionKey.js";
@@ -16,6 +17,7 @@ const run = promisify(execFile);
16
17
  */
17
18
  const DEFAULT_SERVER = process.env.RELAY_SERVER ?? "https://relay-production-c9bd.up.railway.app";
18
19
  const DEFAULT_WEB = process.env.RELAY_WEB ?? "https://relay-web-green.vercel.app";
20
+ const DEFAULT_MODEL = process.env.RELAY_MODEL ?? "claude-sonnet-5";
19
21
  const USAGE = `relayrun — run a Relay session against a repository on this machine
20
22
 
21
23
  relayrun [options]
@@ -27,6 +29,7 @@ const USAGE = `relayrun — run a Relay session against a repository on this mac
27
29
  Costs nothing, but ignores what you type and replays
28
30
  a fixed script. For UI work, not for real answers.
29
31
  --api-key <key> Bill runs to this key instead of your Claude Code login
32
+ --model <id> Model to run (default: ${DEFAULT_MODEL})
30
33
  --session <id> Reattach to an existing session (requires --token)
31
34
  --token <token> Runner token for --session
32
35
  --github-repo <o/n> Open a PR here on publish
@@ -123,6 +126,14 @@ async function resolveCredentials(args) {
123
126
  if (!key) {
124
127
  // No explicit key: the SDK uses whatever this machine is already logged in
125
128
  // with, exactly as Claude Code does.
129
+ //
130
+ // Checked now rather than left to fail on the first instruction. By then a
131
+ // link has been printed and shared, so the failure lands in front of
132
+ // whoever was invited — and reads as "this tool is broken" rather than
133
+ // "you need to log in".
134
+ if ((await findClaudeLogin()) === "missing") {
135
+ throw new Error(noLoginGuidance(await hasClaudeCli()));
136
+ }
126
137
  return { mode: "real", keySource: "oauth", keyHint: null };
127
138
  }
128
139
  if (!looksLikeApiKey(key)) {
@@ -171,6 +182,7 @@ async function main() {
171
182
  console.log(`\n repo ${path.basename(sourcePath)} (${sourcePath})`);
172
183
  console.log(` agent ${billing}`);
173
184
  console.log(` session ${id}`);
185
+ console.log(` shared transcript only — code, keys and files stay on this machine`);
174
186
  console.log(`\n Share this link:\n ${new URL(`/session/${id}`, webUrl)}\n`);
175
187
  if (!existing) {
176
188
  console.log(` To reattach after a restart:\n relayrun --session ${id} --token ${runnerToken}\n`);
@@ -184,6 +196,7 @@ async function main() {
184
196
  keySource: credentials.keySource,
185
197
  keyHint: credentials.keyHint,
186
198
  apiKeyHelper: credentials.apiKeyHelper,
199
+ model: str(args, "model") ?? DEFAULT_MODEL,
187
200
  });
188
201
  }
189
202
  main().catch((err) => {
package/dist/login.js ADDED
@@ -0,0 +1,81 @@
1
+ import { execFile } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import path from "node:path";
5
+ import { promisify } from "node:util";
6
+ const run = promisify(execFile);
7
+ // Probed for existence only. `security` prints the secret itself just for -w,
8
+ // which is deliberately not passed — nothing here needs the credential, only
9
+ // the knowledge that one exists.
10
+ const KEYCHAIN_SERVICE = "Claude Code-credentials";
11
+ const PROBE_TIMEOUT_MS = 5_000;
12
+ export async function findClaudeLogin() {
13
+ if (process.platform === "darwin") {
14
+ try {
15
+ await run("security", ["find-generic-password", "-s", KEYCHAIN_SERVICE], {
16
+ timeout: PROBE_TIMEOUT_MS,
17
+ });
18
+ return "found";
19
+ }
20
+ catch {
21
+ return "missing";
22
+ }
23
+ }
24
+ if (process.platform === "linux") {
25
+ return existsSync(path.join(homedir(), ".claude", ".credentials.json"))
26
+ ? "found"
27
+ : "missing";
28
+ }
29
+ return "unknown";
30
+ }
31
+ /** Whether the Claude Code CLI is on PATH, so the advice can skip an install step. */
32
+ export async function hasClaudeCli() {
33
+ try {
34
+ await run("command", ["-v", "claude"], { shell: true, timeout: PROBE_TIMEOUT_MS });
35
+ return true;
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ }
41
+ /**
42
+ * Printed instead of a session link when no credential is found.
43
+ *
44
+ * Line one carries no indent because the caller's error handler adds it; the
45
+ * rest is indented here.
46
+ *
47
+ * Two variants because the install step is noise for the many people who
48
+ * already have the CLI and simply are not signed in — telling them to install
49
+ * something they have reads as advice that hasn't looked at their machine.
50
+ *
51
+ * There is deliberately no "click here to log in" link: the sign-in is an
52
+ * OAuth flow the CLI has to start itself, since it generates the challenge and
53
+ * receives the callback that writes the credential. No static URL can do it.
54
+ */
55
+ export function noLoginGuidance(cliInstalled) {
56
+ const signIn = cliInstalled
57
+ ? ` 1 Sign in to Claude Code (recommended)
58
+ claude → sign in, then re-run relayrun
59
+ The CLI is already installed here — it just isn't signed in.`
60
+ : ` 1 Install and sign in to Claude Code (recommended)
61
+ npm i -g @anthropic-ai/claude-code
62
+ claude → sign in, then re-run relayrun
63
+ Setup guide: https://docs.claude.com/en/docs/claude-code/setup`;
64
+ return `No Claude credential found on this machine
65
+
66
+ relayrun ships no credentials of its own. It runs Claude on your machine,
67
+ with your access, billed to your account — so it needs one of these first:
68
+
69
+ ${signIn}
70
+
71
+ 2 Use an Anthropic API key
72
+ relayrun --api-key sk-ant-...
73
+ Create one at https://console.anthropic.com/settings/keys
74
+
75
+ 3 See how it works without spending anything
76
+ relayrun --mock → scripted demo, ignores your instructions
77
+
78
+ Signing in through the Claude Code VS Code extension works too — it shares
79
+ the same credential. A claude.ai browser session or the Claude Desktop app
80
+ does not.`;
81
+ }
package/dist/runner.js CHANGED
@@ -47,6 +47,7 @@ export function startRunner(opts) {
47
47
  signal: abort.signal,
48
48
  resume,
49
49
  apiKeyHelper: opts.apiKeyHelper,
50
+ model: opts.model,
50
51
  });
51
52
  }
52
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relayrun",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Run a Relay session against a repository on your own machine — everyone with the link watches the agent work, one person drives.",
5
5
  "keywords": [
6
6
  "claude",