peekable 0.1.4 → 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/api.d.ts +1 -0
- package/dist/api.js +19 -1
- package/dist/command-error.d.ts +8 -0
- package/dist/command-error.js +29 -2
- package/dist/commands/close.js +18 -8
- package/dist/commands/create.js +17 -14
- package/dist/commands/doctor.d.ts +28 -0
- package/dist/commands/doctor.js +109 -9
- package/dist/commands/feedback.js +53 -43
- package/dist/commands/init.js +48 -42
- package/dist/commands/list.js +32 -22
- package/dist/commands/proxy.js +45 -13
- package/dist/commands/push-url.js +22 -19
- package/dist/commands/push.js +24 -18
- package/dist/commands/register.js +49 -44
- package/dist/commands/report.d.ts +11 -0
- package/dist/commands/report.js +107 -0
- package/dist/commands/resolve.js +19 -9
- package/dist/commands/uninstall.js +14 -11
- package/dist/commands/watch.js +37 -3
- package/dist/config.d.ts +16 -0
- package/dist/config.js +64 -15
- package/dist/index.js +34 -1
- package/dist/telemetry.d.ts +26 -0
- package/dist/telemetry.js +297 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -3
package/dist/api.d.ts
CHANGED
package/dist/api.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { CLI_VERSION } from "./version.js";
|
|
1
2
|
export const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
|
|
3
|
+
export const USER_AGENT = `peekable-cli/${CLI_VERSION} (${process.platform}; ${process.arch}; node ${process.version})`;
|
|
2
4
|
export class ApiError extends Error {
|
|
3
5
|
status;
|
|
4
6
|
details;
|
|
@@ -15,6 +17,7 @@ export async function api(config, method, path, body, opts = {}) {
|
|
|
15
17
|
headers: {
|
|
16
18
|
Authorization: `Bearer ${config.api_key}`,
|
|
17
19
|
"Content-Type": "application/json",
|
|
20
|
+
"User-Agent": USER_AGENT,
|
|
18
21
|
},
|
|
19
22
|
body: body ? JSON.stringify(body) : undefined,
|
|
20
23
|
}, opts.timeoutMs);
|
|
@@ -29,7 +32,10 @@ export async function postDebugEvent(config, body) {
|
|
|
29
32
|
export async function apiNoAuth(url, method, path, body, opts = {}) {
|
|
30
33
|
const res = await fetchWithTimeout(`${url}/api${path}`, {
|
|
31
34
|
method,
|
|
32
|
-
headers: {
|
|
35
|
+
headers: {
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
"User-Agent": USER_AGENT,
|
|
38
|
+
},
|
|
33
39
|
body: body ? JSON.stringify(body) : undefined,
|
|
34
40
|
}, opts.timeoutMs);
|
|
35
41
|
if (!res.ok) {
|
|
@@ -40,9 +46,14 @@ export async function apiNoAuth(url, method, path, body, opts = {}) {
|
|
|
40
46
|
export async function fetchWithTimeout(input, init = {}, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
|
|
41
47
|
const controller = new AbortController();
|
|
42
48
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
49
|
+
const headers = new Headers(init.headers);
|
|
50
|
+
if (!headers.has("User-Agent")) {
|
|
51
|
+
headers.set("User-Agent", USER_AGENT);
|
|
52
|
+
}
|
|
43
53
|
try {
|
|
44
54
|
return await fetch(input, {
|
|
45
55
|
...init,
|
|
56
|
+
headers,
|
|
46
57
|
signal: init.signal ?? controller.signal,
|
|
47
58
|
});
|
|
48
59
|
}
|
|
@@ -61,5 +72,12 @@ async function buildApiError(res) {
|
|
|
61
72
|
const body = details && typeof details === "object" && !Array.isArray(details)
|
|
62
73
|
? details
|
|
63
74
|
: { error: res.statusText };
|
|
75
|
+
// Every response carries X-Request-Id, but only 500 bodies embed it —
|
|
76
|
+
// lift it from the header so 4xx failures stay correlatable too.
|
|
77
|
+
if (typeof body.request_id !== "string") {
|
|
78
|
+
const headerRequestId = res.headers.get("x-request-id");
|
|
79
|
+
if (headerRequestId)
|
|
80
|
+
body.request_id = headerRequestId;
|
|
81
|
+
}
|
|
64
82
|
return new ApiError(res.status, body, res.statusText);
|
|
65
83
|
}
|
package/dist/command-error.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
export declare class CommandFailure extends Error {
|
|
2
|
+
exitCode: number;
|
|
3
|
+
cause?: unknown;
|
|
4
|
+
constructor(message: string, opts?: {
|
|
5
|
+
exitCode?: number;
|
|
6
|
+
cause?: unknown;
|
|
7
|
+
});
|
|
8
|
+
}
|
|
1
9
|
export declare function errorPayload(err: unknown): Record<string, unknown>;
|
|
2
10
|
export declare function getErrorMessage(err: unknown): string;
|
|
3
11
|
export declare function printCommandError(prefix: string, err: unknown, json?: boolean): void;
|
package/dist/command-error.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { ApiError } from "./api.js";
|
|
2
|
+
export class CommandFailure extends Error {
|
|
3
|
+
exitCode;
|
|
4
|
+
cause;
|
|
5
|
+
constructor(message, opts = {}) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "CommandFailure";
|
|
8
|
+
this.exitCode = opts.exitCode ?? 1;
|
|
9
|
+
this.cause = opts.cause;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
2
12
|
export function errorPayload(err) {
|
|
3
13
|
if (err instanceof ApiError) {
|
|
4
14
|
return {
|
|
@@ -10,14 +20,31 @@ export function errorPayload(err) {
|
|
|
10
20
|
return { error: getErrorMessage(err) };
|
|
11
21
|
}
|
|
12
22
|
export function getErrorMessage(err) {
|
|
23
|
+
if (err instanceof CommandFailure) {
|
|
24
|
+
if (err.cause instanceof Error)
|
|
25
|
+
return err.cause.message;
|
|
26
|
+
return err.message;
|
|
27
|
+
}
|
|
13
28
|
if (err instanceof Error)
|
|
14
29
|
return err.message;
|
|
15
30
|
return String(err);
|
|
16
31
|
}
|
|
32
|
+
function getRequestId(err) {
|
|
33
|
+
const source = err instanceof CommandFailure ? err.cause : err;
|
|
34
|
+
if (source instanceof ApiError) {
|
|
35
|
+
const rid = source.details.request_id;
|
|
36
|
+
if (typeof rid === "string" && rid)
|
|
37
|
+
return rid;
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
17
41
|
export function printCommandError(prefix, err, json) {
|
|
42
|
+
const cause = err instanceof CommandFailure && err.cause !== undefined ? err.cause : err;
|
|
18
43
|
if (json) {
|
|
19
|
-
console.log(JSON.stringify(errorPayload(
|
|
44
|
+
console.log(JSON.stringify(errorPayload(cause)));
|
|
20
45
|
return;
|
|
21
46
|
}
|
|
22
|
-
|
|
47
|
+
const requestId = getRequestId(err);
|
|
48
|
+
const suffix = requestId ? ` (request_id: ${requestId})` : "";
|
|
49
|
+
console.error(`${prefix}: ${getErrorMessage(err)}${suffix}`);
|
|
23
50
|
}
|
package/dist/commands/close.js
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { requireConfig } from "../config.js";
|
|
3
3
|
import { api } from "../api.js";
|
|
4
|
+
import { CommandFailure, printCommandError } from "../command-error.js";
|
|
5
|
+
import { runWithTelemetry } from "../telemetry.js";
|
|
4
6
|
export const closeCommand = new Command("close")
|
|
5
7
|
.argument("<session-id>", "Session ID")
|
|
6
8
|
.description("Close a session")
|
|
7
9
|
.option("--json", "Output JSON")
|
|
8
10
|
.action(async (sessionId, opts) => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
await runWithTelemetry("close", async () => {
|
|
12
|
+
try {
|
|
13
|
+
const config = requireConfig();
|
|
14
|
+
await api(config, "DELETE", `/sessions/${sessionId}`);
|
|
15
|
+
if (opts.json) {
|
|
16
|
+
console.log(JSON.stringify({ ok: true }));
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
console.log(`Closed session ${sessionId}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
printCommandError("Close failed", err, opts.json);
|
|
24
|
+
throw new CommandFailure("Close failed", { cause: err });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
17
27
|
});
|
package/dist/commands/create.js
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { requireConfig } from "../config.js";
|
|
3
3
|
import { api } from "../api.js";
|
|
4
|
-
import { printCommandError } from "../command-error.js";
|
|
4
|
+
import { CommandFailure, printCommandError } from "../command-error.js";
|
|
5
|
+
import { runWithTelemetry } from "../telemetry.js";
|
|
5
6
|
export const createCommand = new Command("create")
|
|
6
7
|
.argument("<name>", "Session name")
|
|
7
8
|
.description("Create a new sharing session")
|
|
8
9
|
.option("--json", "Output JSON")
|
|
9
10
|
.action(async (name, opts) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
await runWithTelemetry("create", async () => {
|
|
12
|
+
try {
|
|
13
|
+
const config = requireConfig();
|
|
14
|
+
const data = await api(config, "POST", "/sessions", { name });
|
|
15
|
+
if (opts.json) {
|
|
16
|
+
console.log(JSON.stringify(data));
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
console.log(`Created session: ${data.id}`);
|
|
20
|
+
console.log(`URL: ${data.url}`);
|
|
21
|
+
}
|
|
15
22
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
catch (err) {
|
|
24
|
+
printCommandError("Create failed", err, opts.json);
|
|
25
|
+
throw new CommandFailure("Create failed", { cause: err });
|
|
19
26
|
}
|
|
20
|
-
}
|
|
21
|
-
catch (err) {
|
|
22
|
-
printCommandError("Create failed", err, opts.json);
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
27
|
+
});
|
|
25
28
|
});
|
|
@@ -3,19 +3,47 @@ export type DoctorStatus = "ok" | "failed" | "skipped";
|
|
|
3
3
|
export interface DoctorReport {
|
|
4
4
|
cliVersion: string;
|
|
5
5
|
nodeVersion: string;
|
|
6
|
+
os: string;
|
|
7
|
+
osRelease: string;
|
|
8
|
+
arch: string;
|
|
9
|
+
isTty: boolean;
|
|
6
10
|
configPath: string;
|
|
7
11
|
configured: boolean;
|
|
8
12
|
serverUrl: string | null;
|
|
9
13
|
health: DoctorStatus;
|
|
10
14
|
auth: DoctorStatus;
|
|
15
|
+
identity: DoctorStatus;
|
|
16
|
+
identityName?: string | null;
|
|
17
|
+
identityEmail?: string | null;
|
|
11
18
|
activeSessions: number | null;
|
|
12
19
|
maxActiveSessions: number | null;
|
|
13
20
|
activeSessionUnlimited?: boolean;
|
|
14
21
|
testPush: DoctorStatus;
|
|
15
22
|
errors?: string[];
|
|
16
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Scalar-only snapshot suitable for authenticated telemetry. Excludes raw
|
|
26
|
+
* error strings, identity fields, and any path or free-form text.
|
|
27
|
+
*/
|
|
28
|
+
export interface DoctorScalarSnapshot {
|
|
29
|
+
cli_version: string;
|
|
30
|
+
node_version: string;
|
|
31
|
+
os: string;
|
|
32
|
+
os_release: string;
|
|
33
|
+
arch: string;
|
|
34
|
+
is_tty: boolean;
|
|
35
|
+
configured: boolean;
|
|
36
|
+
health: DoctorStatus;
|
|
37
|
+
auth: DoctorStatus;
|
|
38
|
+
identity: DoctorStatus;
|
|
39
|
+
test_push: DoctorStatus;
|
|
40
|
+
active_sessions: number | null;
|
|
41
|
+
max_active_sessions: number | null;
|
|
42
|
+
active_session_unlimited: boolean;
|
|
43
|
+
}
|
|
17
44
|
export declare const doctorCommand: Command;
|
|
18
45
|
export declare function runDoctor(opts?: {
|
|
19
46
|
testPush?: boolean;
|
|
20
47
|
}): Promise<DoctorReport>;
|
|
48
|
+
export declare function toScalarSnapshot(report: DoctorReport): DoctorScalarSnapshot;
|
|
21
49
|
export declare function formatDoctorReport(report: DoctorReport): string;
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,31 +1,84 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { release } from "os";
|
|
3
|
+
import { api, fetchWithTimeout, postDebugEvent } from "../api.js";
|
|
4
|
+
import { ConfigError, getConfigPath, readConfig, tryReadConfig } from "../config.js";
|
|
5
|
+
import { CommandFailure, getErrorMessage } from "../command-error.js";
|
|
5
6
|
import { formatQuota } from "../quota.js";
|
|
6
7
|
import { CLI_VERSION } from "../version.js";
|
|
8
|
+
import { runWithTelemetry } from "../telemetry.js";
|
|
7
9
|
export const doctorCommand = new Command("doctor")
|
|
8
10
|
.description("Print a support snapshot for debugging Peekable setup")
|
|
9
11
|
.option("--json", "Output JSON")
|
|
10
12
|
.option("--test-push", "Create, push, and close a temporary test session")
|
|
13
|
+
.option("--report", "Send a scalar snapshot to Peekable as authenticated telemetry")
|
|
11
14
|
.action(async (opts) => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
await runWithTelemetry("doctor", async () => {
|
|
16
|
+
const report = await runDoctor({ testPush: Boolean(opts.testPush) });
|
|
17
|
+
if (opts.json) {
|
|
18
|
+
console.log(JSON.stringify(report));
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
console.log(formatDoctorReport(report));
|
|
22
|
+
}
|
|
23
|
+
if (opts.report) {
|
|
24
|
+
const configResult = tryReadConfig();
|
|
25
|
+
if (configResult.state !== "ok") {
|
|
26
|
+
// Distinguish corrupt from missing so telemetry classifies the
|
|
27
|
+
// beacon as cli_config_corrupt rather than the generic catch-all.
|
|
28
|
+
if (configResult.state === "corrupt") {
|
|
29
|
+
const msg = "Cannot send doctor report: config file is corrupted. Run `peekable register` to reconfigure.";
|
|
30
|
+
console.error(msg);
|
|
31
|
+
throw new CommandFailure(msg, {
|
|
32
|
+
cause: new ConfigError("corrupt", configResult.parseError),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
console.error("Cannot send doctor report: not configured. Run `peekable register` first.");
|
|
36
|
+
throw new CommandFailure("Not configured");
|
|
37
|
+
}
|
|
38
|
+
const config = configResult.config;
|
|
39
|
+
const snapshot = toScalarSnapshot(report);
|
|
40
|
+
if (!opts.json) {
|
|
41
|
+
console.log("\nSending doctor report:");
|
|
42
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
await postDebugEvent(config, {
|
|
46
|
+
event_name: "cli_doctor_ran",
|
|
47
|
+
source: "cli",
|
|
48
|
+
command: "doctor",
|
|
49
|
+
cli_version: CLI_VERSION,
|
|
50
|
+
status: "ok",
|
|
51
|
+
metadata: {
|
|
52
|
+
...snapshot,
|
|
53
|
+
diagnostics_json: JSON.stringify(snapshot),
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
if (!opts.json)
|
|
57
|
+
console.log("Report sent.");
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
if (!opts.json)
|
|
61
|
+
console.error(`Report send failed: ${getErrorMessage(err)}`);
|
|
62
|
+
throw new CommandFailure("Report send failed", { cause: err });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
18
66
|
});
|
|
19
67
|
export async function runDoctor(opts = {}) {
|
|
20
68
|
const config = readConfig();
|
|
21
69
|
const report = {
|
|
22
70
|
cliVersion: CLI_VERSION,
|
|
23
71
|
nodeVersion: process.version,
|
|
72
|
+
os: process.platform,
|
|
73
|
+
osRelease: release(),
|
|
74
|
+
arch: process.arch,
|
|
75
|
+
isTty: Boolean(process.stdout.isTTY),
|
|
24
76
|
configPath: redactHomePath(getConfigPath()),
|
|
25
77
|
configured: Boolean(config),
|
|
26
78
|
serverUrl: config?.url ?? null,
|
|
27
79
|
health: "skipped",
|
|
28
80
|
auth: "skipped",
|
|
81
|
+
identity: "skipped",
|
|
29
82
|
activeSessions: null,
|
|
30
83
|
maxActiveSessions: null,
|
|
31
84
|
activeSessionUnlimited: false,
|
|
@@ -57,6 +110,19 @@ export async function runDoctor(opts = {}) {
|
|
|
57
110
|
report.auth = "failed";
|
|
58
111
|
report.errors.push(`Auth check failed: ${getErrorMessage(err)}`);
|
|
59
112
|
}
|
|
113
|
+
try {
|
|
114
|
+
const me = await api(config, "GET", "/me");
|
|
115
|
+
report.identity = "ok";
|
|
116
|
+
if (me && typeof me === "object") {
|
|
117
|
+
const anyMe = me;
|
|
118
|
+
report.identityName = typeof anyMe.name === "string" ? anyMe.name : null;
|
|
119
|
+
report.identityEmail = typeof anyMe.email === "string" ? anyMe.email : null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
report.identity = "failed";
|
|
124
|
+
report.errors.push(`Identity check failed: ${getErrorMessage(err)}`);
|
|
125
|
+
}
|
|
60
126
|
if (opts.testPush && report.auth === "ok") {
|
|
61
127
|
try {
|
|
62
128
|
const session = await api(config, "POST", "/sessions", { name: "__peekable_doctor__" });
|
|
@@ -79,13 +145,34 @@ export async function runDoctor(opts = {}) {
|
|
|
79
145
|
delete report.errors;
|
|
80
146
|
return report;
|
|
81
147
|
}
|
|
148
|
+
export function toScalarSnapshot(report) {
|
|
149
|
+
return {
|
|
150
|
+
cli_version: report.cliVersion,
|
|
151
|
+
node_version: report.nodeVersion,
|
|
152
|
+
os: report.os,
|
|
153
|
+
os_release: report.osRelease,
|
|
154
|
+
arch: report.arch,
|
|
155
|
+
is_tty: report.isTty,
|
|
156
|
+
configured: report.configured,
|
|
157
|
+
health: report.health,
|
|
158
|
+
auth: report.auth,
|
|
159
|
+
identity: report.identity,
|
|
160
|
+
test_push: report.testPush,
|
|
161
|
+
active_sessions: report.activeSessions,
|
|
162
|
+
max_active_sessions: report.maxActiveSessions,
|
|
163
|
+
active_session_unlimited: Boolean(report.activeSessionUnlimited),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
82
166
|
export function formatDoctorReport(report) {
|
|
83
167
|
const lines = [
|
|
84
168
|
`Peekable CLI: ${report.cliVersion}`,
|
|
85
169
|
`Node: ${report.nodeVersion}`,
|
|
170
|
+
`OS: ${report.os} ${report.osRelease} (${report.arch})`,
|
|
171
|
+
`TTY: ${report.isTty ? "yes" : "no"}`,
|
|
86
172
|
`Config: ${report.configured ? report.configPath : "missing"}`,
|
|
87
173
|
`Server: ${formatServer(report)}`,
|
|
88
174
|
`Auth: ${formatAuth(report.auth)}`,
|
|
175
|
+
`Identity: ${formatIdentity(report)}`,
|
|
89
176
|
`Active sessions: ${formatQuota(report.activeSessions, report.maxActiveSessions, report.activeSessionUnlimited)}`,
|
|
90
177
|
`Test push: ${report.testPush}`,
|
|
91
178
|
];
|
|
@@ -118,6 +205,19 @@ function formatAuth(status) {
|
|
|
118
205
|
return "failed";
|
|
119
206
|
return "not checked";
|
|
120
207
|
}
|
|
208
|
+
function formatIdentity(report) {
|
|
209
|
+
if (report.identity === "ok") {
|
|
210
|
+
const parts = [];
|
|
211
|
+
if (report.identityName)
|
|
212
|
+
parts.push(report.identityName);
|
|
213
|
+
if (report.identityEmail)
|
|
214
|
+
parts.push(`<${report.identityEmail}>`);
|
|
215
|
+
return parts.length ? parts.join(" ") : "ok";
|
|
216
|
+
}
|
|
217
|
+
if (report.identity === "failed")
|
|
218
|
+
return "failed";
|
|
219
|
+
return "not checked";
|
|
220
|
+
}
|
|
121
221
|
async function getUsageSnapshot(config) {
|
|
122
222
|
try {
|
|
123
223
|
return await api(config, "GET", "/me/usage");
|
|
@@ -1,56 +1,66 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { requireConfig } from "../config.js";
|
|
3
3
|
import { api } from "../api.js";
|
|
4
|
+
import { CommandFailure, printCommandError } from "../command-error.js";
|
|
5
|
+
import { runWithTelemetry } from "../telemetry.js";
|
|
4
6
|
export const feedbackCommand = new Command("feedback")
|
|
5
7
|
.argument("<session-id>", "Session ID")
|
|
6
8
|
.description("Fetch feedback for a session")
|
|
7
9
|
.option("--json", "Output JSON")
|
|
8
10
|
.action(async (sessionId, opts) => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const hasAnnotations = annotationsData.annotations && annotationsData.annotations.length > 0;
|
|
20
|
-
if (!hasFeedback && !hasAnnotations) {
|
|
21
|
-
console.log("No feedback yet.");
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
// Display choice feedback
|
|
25
|
-
if (hasFeedback) {
|
|
26
|
-
console.log("\nChoice Feedback:");
|
|
27
|
-
for (const group of eventsData.feedback) {
|
|
28
|
-
console.log(`\n--- Version ${group.version} ---`);
|
|
29
|
-
for (const event of group.events) {
|
|
30
|
-
const who = event.viewer ?? "Anonymous";
|
|
31
|
-
console.log(` ${who} chose "${event.choice}" — ${event.label}`);
|
|
11
|
+
await runWithTelemetry("feedback", async () => {
|
|
12
|
+
try {
|
|
13
|
+
const config = requireConfig();
|
|
14
|
+
const [eventsData, annotationsData] = await Promise.all([
|
|
15
|
+
api(config, "GET", `/sessions/${sessionId}/feedback`),
|
|
16
|
+
api(config, "GET", `/sessions/${sessionId}/annotations`).catch(() => ({ annotations: [] })),
|
|
17
|
+
]);
|
|
18
|
+
if (opts.json) {
|
|
19
|
+
console.log(JSON.stringify({ ...eventsData, ...annotationsData }));
|
|
20
|
+
return;
|
|
32
21
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
.map(([key, val]) => `${key}: ${val}`)
|
|
49
|
-
.join(", ");
|
|
50
|
-
if (styles)
|
|
51
|
-
console.log(` Styles: ${styles}`);
|
|
22
|
+
const hasFeedback = eventsData.feedback.length > 0;
|
|
23
|
+
const hasAnnotations = annotationsData.annotations && annotationsData.annotations.length > 0;
|
|
24
|
+
if (!hasFeedback && !hasAnnotations) {
|
|
25
|
+
console.log("No feedback yet.");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Display choice feedback
|
|
29
|
+
if (hasFeedback) {
|
|
30
|
+
console.log("\nChoice Feedback:");
|
|
31
|
+
for (const group of eventsData.feedback) {
|
|
32
|
+
console.log(`\n--- Version ${group.version} ---`);
|
|
33
|
+
for (const event of group.events) {
|
|
34
|
+
const who = event.viewer ?? "Anonymous";
|
|
35
|
+
console.log(` ${who} chose "${event.choice}" — ${event.label}`);
|
|
36
|
+
}
|
|
52
37
|
}
|
|
53
38
|
}
|
|
39
|
+
// Display annotations
|
|
40
|
+
if (hasAnnotations) {
|
|
41
|
+
console.log("\nAnnotations:");
|
|
42
|
+
for (const group of annotationsData.annotations) {
|
|
43
|
+
console.log(`\n--- Version ${group.version} ---`);
|
|
44
|
+
for (const ann of group.items) {
|
|
45
|
+
const who = ann.viewer ?? "Anonymous";
|
|
46
|
+
const elementName = ann.element_name || "Unknown";
|
|
47
|
+
const selector = ann.selector || "";
|
|
48
|
+
console.log(` [${who}] ${elementName} (${selector})`);
|
|
49
|
+
console.log(` "${ann.note}"`);
|
|
50
|
+
if (ann.element_context?.computedStyles) {
|
|
51
|
+
const styles = Object.entries(ann.element_context.computedStyles)
|
|
52
|
+
.map(([key, val]) => `${key}: ${val}`)
|
|
53
|
+
.join(", ");
|
|
54
|
+
if (styles)
|
|
55
|
+
console.log(` Styles: ${styles}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
printCommandError("Feedback failed", err, opts.json);
|
|
63
|
+
throw new CommandFailure("Feedback failed", { cause: err });
|
|
54
64
|
}
|
|
55
|
-
}
|
|
65
|
+
});
|
|
56
66
|
});
|
package/dist/commands/init.js
CHANGED
|
@@ -5,6 +5,8 @@ import { join, dirname } from "path";
|
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import { requireConfig } from "../config.js";
|
|
7
7
|
import { api } from "../api.js";
|
|
8
|
+
import { CommandFailure } from "../command-error.js";
|
|
9
|
+
import { runWithTelemetry } from "../telemetry.js";
|
|
8
10
|
import { getClaudeDir, getClaudePeekableSkillDir, getCodexDir, getCodexPeekableSkillDir, } from "../paths.js";
|
|
9
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
12
|
function installPeekableSkill(appName, appDir, skillDir, skillSource, jsonOutput) {
|
|
@@ -57,50 +59,54 @@ export const initCommand = new Command("init")
|
|
|
57
59
|
.description("Set up peekable — install Claude Code/Codex skills and verify connection")
|
|
58
60
|
.option("--json", "Output JSON")
|
|
59
61
|
.action(async (opts) => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
62
|
+
await runWithTelemetry("init", async () => {
|
|
63
|
+
const config = requireConfig();
|
|
64
|
+
const result = { status: "ok" };
|
|
65
|
+
// 1. Test connection
|
|
66
|
+
try {
|
|
67
|
+
const res = await fetch(`${config.url}/health`);
|
|
68
|
+
if (!res.ok)
|
|
69
|
+
throw new Error("Health check failed");
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
const msg = `Cannot reach server at ${config.url}`;
|
|
73
|
+
if (opts.json) {
|
|
74
|
+
console.log(JSON.stringify({ status: "error", error: msg }));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
console.error(msg);
|
|
78
|
+
}
|
|
79
|
+
// Preserve the fetch error so telemetry can classify the connection
|
|
80
|
+
// failure (ECONNREFUSED vs DNS vs timeout).
|
|
81
|
+
throw new CommandFailure(msg, { cause: err });
|
|
82
|
+
}
|
|
83
|
+
if (!opts.json)
|
|
84
|
+
console.log("Connection verified.");
|
|
85
|
+
// 2. Detect local agent tools and install skill
|
|
86
|
+
const skillSource = join(__dirname, "..", "..", "skill", "SKILL.md");
|
|
87
|
+
Object.assign(result, installLocalAgentSkills(skillSource, opts.json));
|
|
88
|
+
// 3. Test push
|
|
89
|
+
try {
|
|
90
|
+
const session = await api(config, "POST", "/sessions", { name: "__share_init_test__" });
|
|
91
|
+
await api(config, "POST", `/sessions/${session.id}/push`, {
|
|
92
|
+
html: "<html><body><p>Share init test</p></body></html>",
|
|
93
|
+
});
|
|
94
|
+
await api(config, "DELETE", `/sessions/${session.id}`);
|
|
95
|
+
result.test_push = "passed";
|
|
96
|
+
if (!opts.json)
|
|
97
|
+
console.log("Test push verified.");
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
result.test_push = "failed";
|
|
101
|
+
result.test_error = err.message;
|
|
102
|
+
if (!opts.json)
|
|
103
|
+
console.error(`Test push failed: ${err.message}`);
|
|
104
|
+
}
|
|
70
105
|
if (opts.json) {
|
|
71
|
-
console.log(JSON.stringify(
|
|
106
|
+
console.log(JSON.stringify(result));
|
|
72
107
|
}
|
|
73
108
|
else {
|
|
74
|
-
console.
|
|
109
|
+
console.log(formatInitNextSteps(result));
|
|
75
110
|
}
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
if (!opts.json)
|
|
79
|
-
console.log("Connection verified.");
|
|
80
|
-
// 2. Detect local agent tools and install skill
|
|
81
|
-
const skillSource = join(__dirname, "..", "..", "skill", "SKILL.md");
|
|
82
|
-
Object.assign(result, installLocalAgentSkills(skillSource, opts.json));
|
|
83
|
-
// 3. Test push
|
|
84
|
-
try {
|
|
85
|
-
const session = await api(config, "POST", "/sessions", { name: "__share_init_test__" });
|
|
86
|
-
await api(config, "POST", `/sessions/${session.id}/push`, {
|
|
87
|
-
html: "<html><body><p>Share init test</p></body></html>",
|
|
88
|
-
});
|
|
89
|
-
await api(config, "DELETE", `/sessions/${session.id}`);
|
|
90
|
-
result.test_push = "passed";
|
|
91
|
-
if (!opts.json)
|
|
92
|
-
console.log("Test push verified.");
|
|
93
|
-
}
|
|
94
|
-
catch (err) {
|
|
95
|
-
result.test_push = "failed";
|
|
96
|
-
result.test_error = err.message;
|
|
97
|
-
if (!opts.json)
|
|
98
|
-
console.error(`Test push failed: ${err.message}`);
|
|
99
|
-
}
|
|
100
|
-
if (opts.json) {
|
|
101
|
-
console.log(JSON.stringify(result));
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
console.log(formatInitNextSteps(result));
|
|
105
|
-
}
|
|
111
|
+
});
|
|
106
112
|
});
|