ztile-cli 0.2.2 → 0.3.0
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/chunk-PDX44BCA.js +11 -0
- package/dist/chunk-XSAVKZSF.js +96 -0
- package/dist/cli.js +35 -19
- package/dist/{daemon-KSDSRNAZ.js → connect-ZGETP6WZ.js} +86 -27
- package/dist/credentials-OO2RZFLG.js +24 -0
- package/dist/disconnect-TZ3MQUZS.js +32 -0
- package/dist/{hook-SN73JWB3.js → hook-ZR4EVEUT.js} +1 -0
- package/dist/login-3IOCLQWW.js +102 -0
- package/dist/{setup-6PUWCUFR.js → setup-VFHRFRZH.js} +8 -18
- package/package.json +2 -2
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
__require
|
|
11
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
__require
|
|
4
|
+
} from "./chunk-PDX44BCA.js";
|
|
5
|
+
|
|
6
|
+
// src/lib/credentials.ts
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
function credentialsPath() {
|
|
10
|
+
const home = process.env["HOME"] ?? process.env["USERPROFILE"] ?? "/tmp";
|
|
11
|
+
return join(home, ".ztile", "credentials.json");
|
|
12
|
+
}
|
|
13
|
+
function loadCredentials() {
|
|
14
|
+
const path = credentialsPath();
|
|
15
|
+
if (!existsSync(path)) return null;
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function saveCredentials(creds) {
|
|
23
|
+
const path = credentialsPath();
|
|
24
|
+
const dir = join(path, "..");
|
|
25
|
+
mkdirSync(dir, { recursive: true });
|
|
26
|
+
writeFileSync(path, JSON.stringify(creds, null, 2) + "\n", { mode: 384 });
|
|
27
|
+
}
|
|
28
|
+
function resolveApiKey() {
|
|
29
|
+
const envKey = process.env["ZTILE_API_KEY"];
|
|
30
|
+
if (envKey) return envKey;
|
|
31
|
+
const creds = loadCredentials();
|
|
32
|
+
if (creds?.apiKey) return creds.apiKey;
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
function resolveServerUrl() {
|
|
36
|
+
const envUrl = process.env["ZTILE_SERVER_URL"];
|
|
37
|
+
if (envUrl) return envUrl;
|
|
38
|
+
const creds = loadCredentials();
|
|
39
|
+
if (creds?.serverUrl) return creds.serverUrl;
|
|
40
|
+
return "https://ztile.dev";
|
|
41
|
+
}
|
|
42
|
+
function resolveMachineId() {
|
|
43
|
+
const envId = process.env["ZTILE_MACHINE_ID"];
|
|
44
|
+
if (envId) return envId;
|
|
45
|
+
const creds = loadCredentials();
|
|
46
|
+
if (creds?.machineId) return creds.machineId;
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
function pidFilePath() {
|
|
50
|
+
const home = process.env["HOME"] ?? process.env["USERPROFILE"] ?? "/tmp";
|
|
51
|
+
return join(home, ".ztile", "connect.pid");
|
|
52
|
+
}
|
|
53
|
+
function savePid(pid) {
|
|
54
|
+
const path = pidFilePath();
|
|
55
|
+
const dir = join(path, "..");
|
|
56
|
+
mkdirSync(dir, { recursive: true });
|
|
57
|
+
writeFileSync(path, String(pid));
|
|
58
|
+
}
|
|
59
|
+
function loadPid() {
|
|
60
|
+
const path = pidFilePath();
|
|
61
|
+
if (!existsSync(path)) return null;
|
|
62
|
+
try {
|
|
63
|
+
const pid = parseInt(readFileSync(path, "utf-8").trim(), 10);
|
|
64
|
+
return isNaN(pid) ? null : pid;
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function removePid() {
|
|
70
|
+
const path = pidFilePath();
|
|
71
|
+
try {
|
|
72
|
+
const { unlinkSync } = __require("fs");
|
|
73
|
+
unlinkSync(path);
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function isProcessRunning(pid) {
|
|
78
|
+
try {
|
|
79
|
+
process.kill(pid, 0);
|
|
80
|
+
return true;
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
loadCredentials,
|
|
88
|
+
saveCredentials,
|
|
89
|
+
resolveApiKey,
|
|
90
|
+
resolveServerUrl,
|
|
91
|
+
resolveMachineId,
|
|
92
|
+
savePid,
|
|
93
|
+
loadPid,
|
|
94
|
+
removePid,
|
|
95
|
+
isProcessRunning
|
|
96
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -3,37 +3,53 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
var args = process.argv.slice(2);
|
|
5
5
|
if (args[0] === "hook") {
|
|
6
|
-
const { handleHook } = await import("./hook-
|
|
6
|
+
const { handleHook } = await import("./hook-ZR4EVEUT.js");
|
|
7
7
|
await handleHook();
|
|
8
8
|
process.exit(0);
|
|
9
9
|
}
|
|
10
|
-
if (args[0] === "
|
|
11
|
-
const {
|
|
12
|
-
|
|
10
|
+
if (args[0] === "login") {
|
|
11
|
+
const { login } = await import("./login-3IOCLQWW.js");
|
|
12
|
+
await login({
|
|
13
|
+
apiKey: findArg(args, "--api-key"),
|
|
14
|
+
serverUrl: findArg(args, "--server-url"),
|
|
15
|
+
machineId: findArg(args, "--machine-id")
|
|
16
|
+
});
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
if (args[0] === "install") {
|
|
20
|
+
const { resolveApiKey } = await import("./credentials-OO2RZFLG.js");
|
|
21
|
+
const apiKey = resolveApiKey();
|
|
13
22
|
if (!apiKey) {
|
|
14
|
-
console.error("Error:
|
|
15
|
-
console.error("
|
|
23
|
+
console.error("Error: Not logged in.");
|
|
24
|
+
console.error(" Run `ztile login` first.");
|
|
16
25
|
process.exit(1);
|
|
17
26
|
}
|
|
18
|
-
await
|
|
19
|
-
|
|
20
|
-
serverUrl: process.env["ZTILE_SERVER_URL"] ?? findArg(args, "--server-url"),
|
|
21
|
-
machineId: process.env["ZTILE_MACHINE_ID"] ?? findArg(args, "--machine-id"),
|
|
22
|
-
project: process.env["ZTILE_PROJECT"] ?? findArg(args, "--project")
|
|
23
|
-
});
|
|
27
|
+
const { setupClaudeCode } = await import("./setup-VFHRFRZH.js");
|
|
28
|
+
await setupClaudeCode();
|
|
24
29
|
process.exit(0);
|
|
25
30
|
}
|
|
26
|
-
if (args[0] === "
|
|
27
|
-
const {
|
|
31
|
+
if (args[0] === "connect") {
|
|
32
|
+
const { runConnect } = await import("./connect-ZGETP6WZ.js");
|
|
28
33
|
const projectDir = findArg(args, "--project") ?? process.env["ZTILE_PROJECT"] ?? process.cwd();
|
|
29
|
-
|
|
34
|
+
const daemon = args.includes("--daemon") || args.includes("-d");
|
|
35
|
+
await runConnect({ projectDir, daemon });
|
|
36
|
+
}
|
|
37
|
+
if (args[0] === "disconnect") {
|
|
38
|
+
const { disconnect } = await import("./disconnect-TZ3MQUZS.js");
|
|
39
|
+
disconnect();
|
|
40
|
+
process.exit(0);
|
|
30
41
|
}
|
|
31
|
-
if (args.length === 0 || !["hook", "
|
|
42
|
+
if (args.length === 0 || !["hook", "install", "connect", "disconnect", "login"].includes(args[0])) {
|
|
32
43
|
if (args.length > 0) console.error(`Unknown command: ${args[0]}`);
|
|
33
44
|
console.log("Usage:");
|
|
34
|
-
console.log(" ztile
|
|
35
|
-
console.log(" ztile
|
|
36
|
-
console.log(" ztile
|
|
45
|
+
console.log(" ztile login Save API key and server config");
|
|
46
|
+
console.log(" ztile install Configure Claude Code hooks");
|
|
47
|
+
console.log(" ztile connect Connect to dashboard for remote control");
|
|
48
|
+
console.log(" ztile disconnect Stop background connection");
|
|
49
|
+
console.log(" ztile hook Handle Claude Code hook (internal)");
|
|
50
|
+
console.log("");
|
|
51
|
+
console.log("Options:");
|
|
52
|
+
console.log(" ztile connect -d Run in background (daemon mode)");
|
|
37
53
|
process.exit(args.length > 0 ? 1 : 0);
|
|
38
54
|
}
|
|
39
55
|
function findArg(args2, name) {
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
isProcessRunning,
|
|
4
|
+
loadPid,
|
|
5
|
+
removePid,
|
|
6
|
+
resolveApiKey,
|
|
7
|
+
resolveServerUrl,
|
|
8
|
+
savePid
|
|
9
|
+
} from "./chunk-XSAVKZSF.js";
|
|
10
|
+
import "./chunk-PDX44BCA.js";
|
|
2
11
|
|
|
3
|
-
// src/commands/
|
|
12
|
+
// src/commands/connect.ts
|
|
4
13
|
import { hostname } from "os";
|
|
5
14
|
|
|
6
15
|
// src/lib/workspace.ts
|
|
@@ -49,11 +58,10 @@ function getWorkspaceId(projectDir) {
|
|
|
49
58
|
}
|
|
50
59
|
|
|
51
60
|
// src/lib/server-client.ts
|
|
52
|
-
var DEFAULT_SERVER_URL = "https://ztile.dev";
|
|
53
61
|
function getConfig() {
|
|
54
62
|
return {
|
|
55
|
-
serverUrl:
|
|
56
|
-
apiKey:
|
|
63
|
+
serverUrl: resolveServerUrl(),
|
|
64
|
+
apiKey: resolveApiKey()
|
|
57
65
|
};
|
|
58
66
|
}
|
|
59
67
|
async function request(method, path, body) {
|
|
@@ -228,77 +236,128 @@ function getSessionList() {
|
|
|
228
236
|
return result;
|
|
229
237
|
}
|
|
230
238
|
|
|
231
|
-
// src/commands/
|
|
239
|
+
// src/commands/connect.ts
|
|
232
240
|
var POLL_INTERVAL_MS = 5e3;
|
|
233
241
|
var HEARTBEAT_INTERVAL_MS = 15e3;
|
|
234
|
-
|
|
235
|
-
|
|
242
|
+
var DIM = "\x1B[2m";
|
|
243
|
+
var RESET = "\x1B[0m";
|
|
244
|
+
var GREEN = "\x1B[32m";
|
|
245
|
+
var YELLOW = "\x1B[33m";
|
|
246
|
+
var CYAN = "\x1B[36m";
|
|
247
|
+
var RED = "\x1B[31m";
|
|
248
|
+
var BOLD = "\x1B[1m";
|
|
249
|
+
function timestamp() {
|
|
250
|
+
return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
|
|
251
|
+
}
|
|
252
|
+
function log2(icon, msg, color = "") {
|
|
253
|
+
console.log(`${DIM}${timestamp()}${RESET} ${color}${icon}${RESET} ${msg}`);
|
|
236
254
|
}
|
|
237
255
|
async function handleCommand(cmd, workspaceId, projectDir) {
|
|
238
|
-
log2(`command: ${cmd.type} (${cmd.id})`);
|
|
239
|
-
await updateCommand(cmd.id, "processing");
|
|
240
256
|
switch (cmd.type) {
|
|
241
257
|
case "spawn": {
|
|
242
258
|
const prompt = cmd.payload.prompt;
|
|
243
259
|
const workdir = cmd.payload.workdir ?? projectDir;
|
|
244
260
|
const allowedTools = cmd.payload.allowedTools;
|
|
261
|
+
log2("\u26A1", `Spawn session: "${prompt.slice(0, 60)}${prompt.length > 60 ? "..." : ""}"`, CYAN);
|
|
262
|
+
await updateCommand(cmd.id, "processing");
|
|
245
263
|
spawnSession(cmd.id, workspaceId, prompt, workdir, allowedTools);
|
|
246
264
|
break;
|
|
247
265
|
}
|
|
248
266
|
case "prompt": {
|
|
249
267
|
if (!cmd.sessionId) {
|
|
268
|
+
log2("\u2717", `Command failed: missing sessionId`, RED);
|
|
250
269
|
await updateCommand(cmd.id, "failed", { error: "Missing sessionId" });
|
|
251
270
|
return;
|
|
252
271
|
}
|
|
253
272
|
const prompt = cmd.payload.prompt;
|
|
273
|
+
log2("\u2192", `Prompt sent: "${prompt.slice(0, 60)}${prompt.length > 60 ? "..." : ""}"`, GREEN);
|
|
274
|
+
await updateCommand(cmd.id, "processing");
|
|
254
275
|
sendPrompt(cmd.id, workspaceId, cmd.sessionId, prompt);
|
|
255
276
|
break;
|
|
256
277
|
}
|
|
257
278
|
default:
|
|
258
|
-
log2(`
|
|
279
|
+
log2("?", `Unknown command: ${cmd.type}`, YELLOW);
|
|
259
280
|
await updateCommand(cmd.id, "failed", { error: `Unknown command type: ${cmd.type}` });
|
|
260
281
|
}
|
|
261
282
|
}
|
|
262
|
-
async function
|
|
263
|
-
const apiKey =
|
|
283
|
+
async function runConnect(options) {
|
|
284
|
+
const apiKey = resolveApiKey();
|
|
264
285
|
if (!apiKey) {
|
|
265
|
-
console.error("Error:
|
|
286
|
+
console.error("Error: Not logged in. Run `ztile login` first.");
|
|
266
287
|
process.exit(1);
|
|
267
288
|
}
|
|
289
|
+
const { projectDir, daemon } = options;
|
|
268
290
|
const workspaceId = getWorkspaceId(projectDir);
|
|
269
291
|
const host = hostname();
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
292
|
+
const serverUrl = resolveServerUrl();
|
|
293
|
+
const existingPid = loadPid();
|
|
294
|
+
if (existingPid && isProcessRunning(existingPid)) {
|
|
295
|
+
console.error(`Error: Already connected (PID: ${existingPid})`);
|
|
296
|
+
console.error(" Run `ztile disconnect` first.");
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
if (daemon) {
|
|
300
|
+
const { spawn } = await import("child_process");
|
|
301
|
+
const child = spawn(process.argv[0], [...process.argv.slice(1).filter((a) => a !== "--daemon" && a !== "-d")], {
|
|
302
|
+
detached: true,
|
|
303
|
+
stdio: "ignore"
|
|
304
|
+
});
|
|
305
|
+
child.unref();
|
|
306
|
+
savePid(child.pid);
|
|
307
|
+
console.log(`Ztile daemon started (PID: ${child.pid})`);
|
|
308
|
+
console.log(` Workspace: ${workspaceId}`);
|
|
309
|
+
console.log(` Server: ${serverUrl}`);
|
|
310
|
+
console.log("");
|
|
311
|
+
console.log("Run `ztile disconnect` to stop.");
|
|
312
|
+
process.exit(0);
|
|
313
|
+
}
|
|
314
|
+
savePid(process.pid);
|
|
315
|
+
console.log("");
|
|
316
|
+
console.log(`${BOLD}Ztile Connect${RESET}`);
|
|
317
|
+
console.log(`${DIM}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${RESET}`);
|
|
318
|
+
console.log(` Server: ${serverUrl}`);
|
|
319
|
+
console.log(` Workspace: ${workspaceId}`);
|
|
320
|
+
console.log(` Hostname: ${host}`);
|
|
321
|
+
console.log(` Project: ${projectDir}`);
|
|
322
|
+
console.log(`${DIM}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${RESET}`);
|
|
323
|
+
console.log("");
|
|
276
324
|
const ok = await sendHeartbeat(workspaceId, host, []);
|
|
277
325
|
if (ok) {
|
|
278
|
-
log2(`
|
|
326
|
+
log2("\u25CF", `Connected to ${serverUrl}`, GREEN);
|
|
279
327
|
} else {
|
|
280
|
-
log2(`
|
|
328
|
+
log2("\u25CF", `Failed to connect (will retry)`, RED);
|
|
281
329
|
}
|
|
330
|
+
log2("\u25CE", `Waiting for commands...`, DIM);
|
|
331
|
+
let lastSessionCount = 0;
|
|
282
332
|
const heartbeatTimer = setInterval(async () => {
|
|
283
333
|
const sessions2 = getSessionList();
|
|
284
|
-
await sendHeartbeat(workspaceId, host, sessions2).catch(() =>
|
|
285
|
-
|
|
334
|
+
const ok2 = await sendHeartbeat(workspaceId, host, sessions2).catch(() => false);
|
|
335
|
+
if (sessions2.length !== lastSessionCount) {
|
|
336
|
+
log2("\u25CE", `${sessions2.length} active session${sessions2.length !== 1 ? "s" : ""}`, DIM);
|
|
337
|
+
lastSessionCount = sessions2.length;
|
|
338
|
+
}
|
|
339
|
+
if (!ok2) {
|
|
340
|
+
log2("!", `Heartbeat failed, retrying...`, YELLOW);
|
|
341
|
+
}
|
|
286
342
|
}, HEARTBEAT_INTERVAL_MS);
|
|
287
|
-
|
|
343
|
+
let commandCount = 0;
|
|
288
344
|
const pollTimer = setInterval(async () => {
|
|
289
345
|
try {
|
|
290
346
|
const commands = await pollCommands(workspaceId);
|
|
291
347
|
for (const cmd of commands) {
|
|
348
|
+
commandCount++;
|
|
292
349
|
await handleCommand(cmd, workspaceId, projectDir);
|
|
293
350
|
}
|
|
294
351
|
} catch (err) {
|
|
295
|
-
log2(`
|
|
352
|
+
log2("!", `Poll error: ${err instanceof Error ? err.message : err}`, RED);
|
|
296
353
|
}
|
|
297
354
|
}, POLL_INTERVAL_MS);
|
|
298
355
|
const shutdown = () => {
|
|
299
|
-
|
|
356
|
+
console.log("");
|
|
357
|
+
log2("\u25CF", `Disconnected (${commandCount} command${commandCount !== 1 ? "s" : ""} processed)`, DIM);
|
|
300
358
|
clearInterval(heartbeatTimer);
|
|
301
359
|
clearInterval(pollTimer);
|
|
360
|
+
removePid();
|
|
302
361
|
process.exit(0);
|
|
303
362
|
};
|
|
304
363
|
process.on("SIGINT", shutdown);
|
|
@@ -307,5 +366,5 @@ async function runDaemon(projectDir) {
|
|
|
307
366
|
});
|
|
308
367
|
}
|
|
309
368
|
export {
|
|
310
|
-
|
|
369
|
+
runConnect
|
|
311
370
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
isProcessRunning,
|
|
4
|
+
loadCredentials,
|
|
5
|
+
loadPid,
|
|
6
|
+
removePid,
|
|
7
|
+
resolveApiKey,
|
|
8
|
+
resolveMachineId,
|
|
9
|
+
resolveServerUrl,
|
|
10
|
+
saveCredentials,
|
|
11
|
+
savePid
|
|
12
|
+
} from "./chunk-XSAVKZSF.js";
|
|
13
|
+
import "./chunk-PDX44BCA.js";
|
|
14
|
+
export {
|
|
15
|
+
isProcessRunning,
|
|
16
|
+
loadCredentials,
|
|
17
|
+
loadPid,
|
|
18
|
+
removePid,
|
|
19
|
+
resolveApiKey,
|
|
20
|
+
resolveMachineId,
|
|
21
|
+
resolveServerUrl,
|
|
22
|
+
saveCredentials,
|
|
23
|
+
savePid
|
|
24
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
isProcessRunning,
|
|
4
|
+
loadPid,
|
|
5
|
+
removePid
|
|
6
|
+
} from "./chunk-XSAVKZSF.js";
|
|
7
|
+
import "./chunk-PDX44BCA.js";
|
|
8
|
+
|
|
9
|
+
// src/commands/disconnect.ts
|
|
10
|
+
function disconnect() {
|
|
11
|
+
const pid = loadPid();
|
|
12
|
+
if (!pid) {
|
|
13
|
+
console.log("No active connection found.");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (!isProcessRunning(pid)) {
|
|
17
|
+
console.log(`Stale PID file (process ${pid} not running). Cleaning up.`);
|
|
18
|
+
removePid();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
process.kill(pid, "SIGTERM");
|
|
23
|
+
removePid();
|
|
24
|
+
console.log(`Disconnected (PID: ${pid})`);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error(`Error stopping process ${pid}: ${err instanceof Error ? err.message : err}`);
|
|
27
|
+
removePid();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
disconnect
|
|
32
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
loadCredentials,
|
|
4
|
+
saveCredentials
|
|
5
|
+
} from "./chunk-XSAVKZSF.js";
|
|
6
|
+
import {
|
|
7
|
+
__require
|
|
8
|
+
} from "./chunk-PDX44BCA.js";
|
|
9
|
+
|
|
10
|
+
// src/commands/login.ts
|
|
11
|
+
import { createInterface } from "readline";
|
|
12
|
+
import { hostname } from "os";
|
|
13
|
+
async function prompt(question) {
|
|
14
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
rl.question(question, (answer) => {
|
|
17
|
+
rl.close();
|
|
18
|
+
resolve(answer.trim());
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function openBrowser(url) {
|
|
23
|
+
const { execSync } = __require("child_process");
|
|
24
|
+
try {
|
|
25
|
+
const platform = process.platform;
|
|
26
|
+
if (platform === "darwin") execSync(`open "${url}"`);
|
|
27
|
+
else if (platform === "win32") execSync(`start "${url}"`);
|
|
28
|
+
else execSync(`xdg-open "${url}" 2>/dev/null || echo ""`);
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function login(options) {
|
|
33
|
+
const existing = loadCredentials();
|
|
34
|
+
const defaultServer = existing?.serverUrl ?? "https://ztile.dev";
|
|
35
|
+
const defaultMachine = existing?.machineId ?? hostname();
|
|
36
|
+
let apiKey = options.apiKey;
|
|
37
|
+
let serverUrl = options.serverUrl ?? defaultServer;
|
|
38
|
+
const machineId = options.machineId ?? defaultMachine;
|
|
39
|
+
if (apiKey) {
|
|
40
|
+
await verifyAndSave(apiKey, serverUrl, machineId);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
console.log("");
|
|
44
|
+
console.log("How would you like to authenticate?");
|
|
45
|
+
console.log("");
|
|
46
|
+
console.log(" 1) Open browser to get API key (recommended)");
|
|
47
|
+
console.log(" 2) Enter API key directly");
|
|
48
|
+
console.log("");
|
|
49
|
+
const choice = await prompt("Choice [1]: ");
|
|
50
|
+
const method = choice === "2" ? "direct" : "browser";
|
|
51
|
+
if (method === "browser") {
|
|
52
|
+
const settingsUrl = `${serverUrl}/settings`;
|
|
53
|
+
console.log("");
|
|
54
|
+
console.log(`Opening ${settingsUrl} ...`);
|
|
55
|
+
console.log("Copy your API key from the Settings page and paste it below.");
|
|
56
|
+
console.log("");
|
|
57
|
+
openBrowser(settingsUrl);
|
|
58
|
+
apiKey = await prompt("API Key: ");
|
|
59
|
+
} else {
|
|
60
|
+
apiKey = await prompt("API Key: ");
|
|
61
|
+
}
|
|
62
|
+
if (!apiKey) {
|
|
63
|
+
console.error("Error: API key is required.");
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
if (!options.serverUrl) {
|
|
67
|
+
const input = await prompt(`Server URL [${defaultServer}]: `);
|
|
68
|
+
if (input) serverUrl = input;
|
|
69
|
+
}
|
|
70
|
+
await verifyAndSave(apiKey, serverUrl, machineId);
|
|
71
|
+
}
|
|
72
|
+
async function verifyAndSave(apiKey, serverUrl, machineId) {
|
|
73
|
+
process.stderr.write("Verifying... ");
|
|
74
|
+
try {
|
|
75
|
+
const res = await fetch(`${serverUrl}/api/hooks/ingest`, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: { "Content-Type": "application/json", "x-api-key": apiKey },
|
|
78
|
+
body: JSON.stringify({})
|
|
79
|
+
});
|
|
80
|
+
if (res.status === 401) {
|
|
81
|
+
console.error("failed");
|
|
82
|
+
console.error("Error: Invalid API key.");
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
console.error("failed");
|
|
87
|
+
console.error(`Error: Could not connect to ${serverUrl}`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
console.error("ok");
|
|
91
|
+
saveCredentials({ apiKey, serverUrl, machineId });
|
|
92
|
+
console.log("");
|
|
93
|
+
console.log("Logged in successfully!");
|
|
94
|
+
console.log(` Server: ${serverUrl}`);
|
|
95
|
+
console.log(` Machine ID: ${machineId}`);
|
|
96
|
+
console.log(` Saved to: ~/.ztile/credentials.json`);
|
|
97
|
+
console.log("");
|
|
98
|
+
console.log("Next: run `ztile install` to configure Claude Code hooks.");
|
|
99
|
+
}
|
|
100
|
+
export {
|
|
101
|
+
login
|
|
102
|
+
};
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import "./chunk-PDX44BCA.js";
|
|
2
3
|
|
|
3
4
|
// src/commands/setup.ts
|
|
4
5
|
import { readFile, writeFile } from "fs/promises";
|
|
5
6
|
import { join } from "path";
|
|
6
7
|
import { existsSync } from "fs";
|
|
7
|
-
import { hostname } from "os";
|
|
8
8
|
import { execSync } from "child_process";
|
|
9
|
-
var DEFAULT_SERVER_URL = "https://ztile.dev";
|
|
10
9
|
var HOOK_EVENTS = [
|
|
11
10
|
"UserPromptSubmit",
|
|
12
11
|
"PreToolUse",
|
|
@@ -20,7 +19,7 @@ var HOOK_EVENTS = [
|
|
|
20
19
|
"Notification",
|
|
21
20
|
"SessionEnd"
|
|
22
21
|
];
|
|
23
|
-
async function setupClaudeCode(
|
|
22
|
+
async function setupClaudeCode(_options) {
|
|
24
23
|
const ztileBin = findGlobalBin();
|
|
25
24
|
if (!ztileBin) {
|
|
26
25
|
console.error("Error: ztile is not globally installed.");
|
|
@@ -46,14 +45,7 @@ async function setupClaudeCode(options) {
|
|
|
46
45
|
} catch {
|
|
47
46
|
}
|
|
48
47
|
const hooks = settings["hooks"] ?? {};
|
|
49
|
-
const
|
|
50
|
-
const envPrefix = [
|
|
51
|
-
`ZTILE_API_KEY=${options.apiKey}`,
|
|
52
|
-
`ZTILE_SERVER_URL=${serverUrl}`,
|
|
53
|
-
`ZTILE_MACHINE_ID=${options.machineId ?? hostname()}`,
|
|
54
|
-
...options.project ? [`ZTILE_PROJECT=${options.project}`] : []
|
|
55
|
-
].join(" ");
|
|
56
|
-
const hookCommand = `${envPrefix} ${ztileBin} hook`;
|
|
48
|
+
const hookCommand = `${ztileBin} hook`;
|
|
57
49
|
const ztileHook = { type: "command", command: hookCommand };
|
|
58
50
|
for (const event of Object.keys(hooks)) {
|
|
59
51
|
const existing = hooks[event] ?? [];
|
|
@@ -75,14 +67,12 @@ async function setupClaudeCode(options) {
|
|
|
75
67
|
settings["hooks"] = hooks;
|
|
76
68
|
await writeFile(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
77
69
|
console.log(`Claude Code: configured ${addedCount} hook(s) in ${settingsPath}`);
|
|
78
|
-
console.log(`
|
|
79
|
-
console.log(`
|
|
80
|
-
console.log(` Machine ID: ${options.machineId ?? hostname()}`);
|
|
81
|
-
if (options.project) console.log(` Project: ${options.project}`);
|
|
82
|
-
console.log(` Binary: ${ztileBin}`);
|
|
83
|
-
console.log(` Events: ${HOOK_EVENTS.join(", ")}`);
|
|
70
|
+
console.log(` Binary: ${ztileBin}`);
|
|
71
|
+
console.log(` Events: ${HOOK_EVENTS.join(", ")}`);
|
|
84
72
|
console.log();
|
|
85
|
-
console.log("
|
|
73
|
+
console.log("Next steps:");
|
|
74
|
+
console.log(" \u2022 Restart Claude Code for hooks to take effect");
|
|
75
|
+
console.log(" \u2022 Run `ztile connect` to enable remote control from the dashboard");
|
|
86
76
|
}
|
|
87
77
|
function findGlobalBin() {
|
|
88
78
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ztile-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Mission control for autonomous coding agents (Claude Code, Codex, Gemini CLI)",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ztile": "dist/cli.js"
|
|
@@ -39,6 +39,6 @@
|
|
|
39
39
|
"type": "module",
|
|
40
40
|
"repository": {
|
|
41
41
|
"type": "git",
|
|
42
|
-
"url": "git+https://github.com/
|
|
42
|
+
"url": "git+https://github.com/shunyooo/ztile.git"
|
|
43
43
|
}
|
|
44
44
|
}
|