@oked/claude-code 0.1.3 → 0.1.5
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/dist/cli.js +98 -8
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from "fs";
|
|
3
3
|
import { hostname } from "os";
|
|
4
4
|
import { join, dirname } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
5
6
|
import { spawn } from "child_process";
|
|
7
|
+
import { createInterface } from "readline";
|
|
6
8
|
import { OKedClient, loadOKedConfig, OKED_CONFIG_PATH } from "@oked/sdk";
|
|
7
9
|
const MCP_TOOL_MATCHER = "mcp__.*";
|
|
8
10
|
const DEFAULT_TOOL_MATCHER = `Bash|Write|Edit|Agent|${MCP_TOOL_MATCHER}`;
|
|
@@ -17,7 +19,18 @@ const HOOK_CONFIG = {
|
|
|
17
19
|
],
|
|
18
20
|
};
|
|
19
21
|
const DEFAULT_BACKEND_URL = process.env.OKED_BACKEND_URL || "https://api.oked.ai";
|
|
20
|
-
|
|
22
|
+
// Derived from this package's package.json at runtime so the version the
|
|
23
|
+
// backend sees (client_version) can never drift from what's actually shipped.
|
|
24
|
+
// From dist/cli.js, package.json sits one dir up at the package root.
|
|
25
|
+
const CLIENT_VERSION = (() => {
|
|
26
|
+
try {
|
|
27
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
28
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8")).version ?? "0.0.0";
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return "0.0.0";
|
|
32
|
+
}
|
|
33
|
+
})();
|
|
21
34
|
function getSettingsPath() {
|
|
22
35
|
return join(process.cwd(), ".claude", "settings.json");
|
|
23
36
|
}
|
|
@@ -82,12 +95,66 @@ function openBrowser(url) {
|
|
|
82
95
|
args = [url];
|
|
83
96
|
}
|
|
84
97
|
try {
|
|
85
|
-
spawn(cmd, args, { detached: true, stdio: "ignore" })
|
|
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;
|
|
86
104
|
}
|
|
87
105
|
catch {
|
|
88
106
|
// Best effort. The user has the URL on screen anyway.
|
|
107
|
+
return false;
|
|
89
108
|
}
|
|
90
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
|
+
}
|
|
91
158
|
async function pair(clientType) {
|
|
92
159
|
const codeRes = await fetch(`${DEFAULT_BACKEND_URL}/api/v1/device/code`, {
|
|
93
160
|
method: "POST",
|
|
@@ -104,14 +171,36 @@ async function pair(clientType) {
|
|
|
104
171
|
}
|
|
105
172
|
const code = (await codeRes.json());
|
|
106
173
|
console.log("");
|
|
107
|
-
console.log("
|
|
108
|
-
console.log(` ${code.verification_uri_complete}`);
|
|
174
|
+
console.log(" Pair this device with your OKed account.");
|
|
109
175
|
console.log("");
|
|
110
|
-
console.log("
|
|
176
|
+
console.log(" Your pairing code:");
|
|
111
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.)`);
|
|
112
202
|
console.log("");
|
|
113
|
-
console.log(" Waiting for confirmation
|
|
114
|
-
openBrowser(code.verification_uri_complete);
|
|
203
|
+
console.log(" Waiting for confirmation...");
|
|
115
204
|
const deadline = Date.now() + code.expires_in * 1000;
|
|
116
205
|
const intervalMs = Math.max(1, code.interval) * 1000;
|
|
117
206
|
while (Date.now() < deadline) {
|
|
@@ -190,9 +279,10 @@ async function init() {
|
|
|
190
279
|
return;
|
|
191
280
|
writeOkedConfig(apiKey, DEFAULT_BACKEND_URL);
|
|
192
281
|
console.log("");
|
|
193
|
-
console.log(` Paired
|
|
282
|
+
console.log(` Paired ✓ Key saved to ${OKED_CONFIG_PATH}`);
|
|
194
283
|
console.log("");
|
|
195
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.");
|
|
196
286
|
}
|
|
197
287
|
async function status() {
|
|
198
288
|
const settingsPath = getSettingsPath();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oked/claude-code",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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.
|
|
46
|
+
"@oked/sdk": "0.1.5"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"typescript": "^5.6.0"
|