@oked/claude-code 0.1.4 → 0.1.6

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 (2) hide show
  1. package/dist/cli.js +85 -7
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -4,6 +4,7 @@ import { hostname } from "os";
4
4
  import { join, dirname } from "path";
5
5
  import { fileURLToPath } from "url";
6
6
  import { spawn } from "child_process";
7
+ import { createInterface } from "readline";
7
8
  import { OKedClient, loadOKedConfig, OKED_CONFIG_PATH } from "@oked/sdk";
8
9
  const MCP_TOOL_MATCHER = "mcp__.*";
9
10
  const DEFAULT_TOOL_MATCHER = `Bash|Write|Edit|Agent|${MCP_TOOL_MATCHER}`;
@@ -94,12 +95,66 @@ function openBrowser(url) {
94
95
  args = [url];
95
96
  }
96
97
  try {
97
- spawn(cmd, args, { detached: true, stdio: "ignore" }).unref();
98
+ const child = spawn(cmd, args, { detached: true, stdio: "ignore" });
99
+ // The launcher may fail asynchronously (e.g. xdg-open missing). Swallow it
100
+ // so it can't crash the process; the URL is always printed as a fallback.
101
+ child.on("error", () => { });
102
+ child.unref();
103
+ return true;
98
104
  }
99
105
  catch {
100
106
  // Best effort. The user has the URL on screen anyway.
107
+ return false;
101
108
  }
102
109
  }
110
+ /**
111
+ * Best-effort copy to the OS clipboard. Resolves true only if a clipboard tool
112
+ * actually accepted the text, so the "(copied to clipboard)" line never lies on
113
+ * a headless box. Linux is tried via wl-copy (Wayland), then xclip, then xsel.
114
+ * The code is always printed, so a failure here is purely cosmetic.
115
+ */
116
+ function copyToClipboard(text) {
117
+ const candidates = process.platform === "darwin"
118
+ ? [{ cmd: "pbcopy", args: [] }]
119
+ : process.platform === "win32"
120
+ ? [{ cmd: "clip", args: [] }]
121
+ : [
122
+ { cmd: "wl-copy", args: [] },
123
+ { cmd: "xclip", args: ["-selection", "clipboard"] },
124
+ { cmd: "xsel", args: ["--clipboard", "--input"] },
125
+ ];
126
+ const tryCopy = ({ cmd, args }) => new Promise((resolve) => {
127
+ let settled = false;
128
+ const done = (ok) => {
129
+ if (!settled) {
130
+ settled = true;
131
+ resolve(ok);
132
+ }
133
+ };
134
+ try {
135
+ const child = spawn(cmd, args, { stdio: ["pipe", "ignore", "ignore"] });
136
+ child.on("error", () => done(false)); // tool missing / not spawnable
137
+ child.on("close", (code) => done(code === 0));
138
+ child.stdin?.on("error", () => { }); // ignore EPIPE if the tool died early
139
+ child.stdin?.end(text);
140
+ }
141
+ catch {
142
+ done(false);
143
+ }
144
+ });
145
+ // Try each candidate in order; stop at the first that succeeds.
146
+ return candidates.reduce((acc, cand) => acc.then((ok) => (ok ? true : tryCopy(cand))), Promise.resolve(false));
147
+ }
148
+ /** Wait for the user to press Enter. Resolves immediately if stdin is closed. */
149
+ function waitForEnter(message) {
150
+ return new Promise((resolve) => {
151
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
152
+ rl.question(message, () => {
153
+ rl.close();
154
+ resolve();
155
+ });
156
+ });
157
+ }
103
158
  async function pair(clientType) {
104
159
  const codeRes = await fetch(`${DEFAULT_BACKEND_URL}/api/v1/device/code`, {
105
160
  method: "POST",
@@ -116,14 +171,36 @@ async function pair(clientType) {
116
171
  }
117
172
  const code = (await codeRes.json());
118
173
  console.log("");
119
- console.log(" To pair this device, open:");
120
- console.log(` ${code.verification_uri_complete}`);
174
+ console.log(" Pair this device with your OKed account.");
121
175
  console.log("");
122
- console.log(" Or visit " + code.verification_uri + " and enter the code:");
176
+ console.log(" Your pairing code:");
123
177
  console.log(` ${code.user_code}`);
178
+ if (await copyToClipboard(code.user_code)) {
179
+ console.log(" (copied to clipboard)");
180
+ }
181
+ console.log("");
182
+ // Let the user read the code and initiate the browser launch themselves, so
183
+ // the popup isn't a surprise. Skip the prompt when non-interactive (no TTY,
184
+ // or `--yes`/`-y`) so CI and scripted installs still work.
185
+ const interactive = Boolean(process.stdin.isTTY) &&
186
+ !process.argv.includes("--yes") &&
187
+ !process.argv.includes("-y");
188
+ if (interactive) {
189
+ await waitForEnter(" Press Enter to open your browser (Ctrl+C to cancel)... ");
190
+ }
191
+ const opened = openBrowser(code.verification_uri_complete);
192
+ console.log("");
193
+ if (opened) {
194
+ console.log(" Opened your browser to:");
195
+ }
196
+ else {
197
+ console.log(" Couldn't open your browser automatically. Open this link:");
198
+ }
199
+ console.log(` ${code.verification_uri_complete}`);
200
+ console.log("");
201
+ console.log(` (Or visit ${code.verification_uri} and enter the code above.)`);
124
202
  console.log("");
125
- console.log(" Waiting for confirmation in your browser...");
126
- openBrowser(code.verification_uri_complete);
203
+ console.log(" Waiting for confirmation...");
127
204
  const deadline = Date.now() + code.expires_in * 1000;
128
205
  const intervalMs = Math.max(1, code.interval) * 1000;
129
206
  while (Date.now() < deadline) {
@@ -202,9 +279,10 @@ async function init() {
202
279
  return;
203
280
  writeOkedConfig(apiKey, DEFAULT_BACKEND_URL);
204
281
  console.log("");
205
- console.log(` Paired. Key saved to ${OKED_CONFIG_PATH}`);
282
+ console.log(` Paired Key saved to ${OKED_CONFIG_PATH}`);
206
283
  console.log("");
207
284
  console.log("Every Claude Code session in this project is now protected.");
285
+ console.log("Open a new Claude Code session to activate the hook.");
208
286
  }
209
287
  async function status() {
210
288
  const settingsPath = getSettingsPath();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oked/claude-code",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "OKed for Claude Code - zero-code human approval for sensitive actions",
5
5
  "type": "module",
6
6
  "bin": {
@@ -43,7 +43,7 @@
43
43
  "access": "public"
44
44
  },
45
45
  "dependencies": {
46
- "@oked/sdk": "0.1.4"
46
+ "@oked/sdk": "0.1.6"
47
47
  },
48
48
  "devDependencies": {
49
49
  "typescript": "^5.6.0"