peekable 0.1.0 → 0.1.2
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 +100 -0
- package/dist/api.d.ts +24 -2
- package/dist/api.js +48 -10
- package/dist/command-error.d.ts +3 -0
- package/dist/command-error.js +23 -0
- package/dist/commands/create.js +13 -6
- package/dist/commands/doctor.d.ts +21 -0
- package/dist/commands/doctor.js +141 -0
- package/dist/commands/feedback.js +38 -8
- package/dist/commands/init.js +3 -3
- package/dist/commands/list.js +13 -2
- package/dist/commands/proxy.d.ts +2 -0
- package/dist/commands/proxy.js +280 -0
- package/dist/commands/push-url.d.ts +2 -0
- package/dist/commands/push-url.js +136 -0
- package/dist/commands/push.js +29 -9
- package/dist/commands/register.js +11 -1
- package/dist/commands/resolve.d.ts +2 -0
- package/dist/commands/resolve.js +20 -0
- package/dist/commands/uninstall.d.ts +21 -0
- package/dist/commands/uninstall.js +81 -0
- package/dist/commands/watch.d.ts +2 -0
- package/dist/commands/watch.js +40 -0
- package/dist/config.js +3 -4
- package/dist/index.js +14 -1
- package/dist/paths.d.ts +4 -0
- package/dist/paths.js +14 -0
- package/dist/quota.d.ts +1 -0
- package/dist/quota.js +7 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +1 -1
- package/skill/SKILL.md +106 -2
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Peekable CLI
|
|
2
|
+
|
|
3
|
+
Share local HTML mockups, HTML response snapshots, and localhost apps with collaborators.
|
|
4
|
+
Reviewers annotate the page in the browser; you pull structured feedback back into
|
|
5
|
+
your terminal.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g peekable
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## First Run
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
peekable register --name "Your Name" --email "you@example.com" --invite-code "your-invite-code"
|
|
17
|
+
peekable init
|
|
18
|
+
peekable create "My first review"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`peekable create` prints a session ID and share URL. Use the session ID with one
|
|
22
|
+
of the publish commands below.
|
|
23
|
+
|
|
24
|
+
## Pick The Right Publish Command
|
|
25
|
+
|
|
26
|
+
Use `push` for a complete standalone HTML file:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
peekable push <session-id> ./out.html
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Use `push-url` when a running page returns a mostly self-contained HTML response
|
|
33
|
+
you want to capture:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
peekable push-url <session-id> http://localhost:3000
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Use `proxy` for a live localhost app with routes, assets, and interactivity:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
peekable proxy 3000 --name "Homepage review"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Pull Feedback
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
peekable feedback <session-id>
|
|
49
|
+
peekable watch <session-id>
|
|
50
|
+
peekable resolve <session-id> <annotation-id>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Free Early Access Limits
|
|
54
|
+
|
|
55
|
+
The hosted free early-access tier allows 3 active sessions per user. Close old
|
|
56
|
+
sessions when you are done:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
peekable list
|
|
60
|
+
peekable close <session-id>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Viewer connections are capped on the hosted service so early access stays
|
|
64
|
+
reliable for everyone.
|
|
65
|
+
|
|
66
|
+
`push-url` snapshots localhost by default. To upload HTML fetched from a remote
|
|
67
|
+
URL, pass `--allow-remote --yes`; for private-network URLs, also pass
|
|
68
|
+
`--allow-private`.
|
|
69
|
+
|
|
70
|
+
## Debug Setup
|
|
71
|
+
|
|
72
|
+
If something feels off, run:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
peekable doctor
|
|
76
|
+
peekable doctor --json
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
For a deeper connectivity check that creates, pushes, and closes a temporary
|
|
80
|
+
session:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
peekable doctor --test-push
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The doctor output intentionally avoids HTML payloads, annotation text, and API
|
|
87
|
+
keys so it is safe to paste into a support thread.
|
|
88
|
+
|
|
89
|
+
## Remove Local Setup
|
|
90
|
+
|
|
91
|
+
To remove Peekable from your machine:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
peekable uninstall
|
|
95
|
+
npm uninstall -g peekable
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`peekable uninstall` removes local config at `~/.peekable` and the installed
|
|
99
|
+
Claude Code skill at `~/.claude/skills/peekable`. It does not delete hosted
|
|
100
|
+
sessions or account data.
|
package/dist/api.d.ts
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
1
|
import { type ShareConfig } from "./config.js";
|
|
2
|
-
export declare
|
|
3
|
-
export declare
|
|
2
|
+
export declare const DEFAULT_REQUEST_TIMEOUT_MS = 15000;
|
|
3
|
+
export declare class ApiError extends Error {
|
|
4
|
+
status: number;
|
|
5
|
+
details: Record<string, unknown>;
|
|
6
|
+
constructor(status: number, details: Record<string, unknown>, fallback: string);
|
|
7
|
+
}
|
|
8
|
+
export declare function api(config: ShareConfig, method: string, path: string, body?: unknown, opts?: {
|
|
9
|
+
timeoutMs?: number;
|
|
10
|
+
}): Promise<any>;
|
|
11
|
+
export declare function postDebugEvent(config: ShareConfig, body: {
|
|
12
|
+
event_name: string;
|
|
13
|
+
session_id?: string;
|
|
14
|
+
source?: "cli";
|
|
15
|
+
command?: string;
|
|
16
|
+
cli_version?: string;
|
|
17
|
+
status?: string;
|
|
18
|
+
duration_ms?: number;
|
|
19
|
+
error_code?: string;
|
|
20
|
+
metadata?: Record<string, unknown>;
|
|
21
|
+
}): Promise<any>;
|
|
22
|
+
export declare function apiNoAuth(url: string, method: string, path: string, body?: unknown, opts?: {
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
}): Promise<any>;
|
|
25
|
+
export declare function fetchWithTimeout(input: string | URL, init?: RequestInit, timeoutMs?: number): Promise<Response>;
|
package/dist/api.js
CHANGED
|
@@ -1,27 +1,65 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
1
|
+
export const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
|
|
2
|
+
export class ApiError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
details;
|
|
5
|
+
constructor(status, details, fallback) {
|
|
6
|
+
super(typeof details.error === "string" ? details.error : fallback);
|
|
7
|
+
this.name = "ApiError";
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.details = details;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export async function api(config, method, path, body, opts = {}) {
|
|
13
|
+
const res = await fetchWithTimeout(`${config.url}/api${path}`, {
|
|
3
14
|
method,
|
|
4
15
|
headers: {
|
|
5
16
|
Authorization: `Bearer ${config.api_key}`,
|
|
6
17
|
"Content-Type": "application/json",
|
|
7
18
|
},
|
|
8
19
|
body: body ? JSON.stringify(body) : undefined,
|
|
9
|
-
});
|
|
20
|
+
}, opts.timeoutMs);
|
|
10
21
|
if (!res.ok) {
|
|
11
|
-
|
|
12
|
-
throw new Error(err.error ?? res.statusText);
|
|
22
|
+
throw await buildApiError(res);
|
|
13
23
|
}
|
|
14
24
|
return res.json();
|
|
15
25
|
}
|
|
16
|
-
export async function
|
|
17
|
-
|
|
26
|
+
export async function postDebugEvent(config, body) {
|
|
27
|
+
return api(config, "POST", "/debug-events", body);
|
|
28
|
+
}
|
|
29
|
+
export async function apiNoAuth(url, method, path, body, opts = {}) {
|
|
30
|
+
const res = await fetchWithTimeout(`${url}/api${path}`, {
|
|
18
31
|
method,
|
|
19
32
|
headers: { "Content-Type": "application/json" },
|
|
20
33
|
body: body ? JSON.stringify(body) : undefined,
|
|
21
|
-
});
|
|
34
|
+
}, opts.timeoutMs);
|
|
22
35
|
if (!res.ok) {
|
|
23
|
-
|
|
24
|
-
throw new Error(err.error ?? res.statusText);
|
|
36
|
+
throw await buildApiError(res);
|
|
25
37
|
}
|
|
26
38
|
return res.json();
|
|
27
39
|
}
|
|
40
|
+
export async function fetchWithTimeout(input, init = {}, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
|
|
41
|
+
const controller = new AbortController();
|
|
42
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
43
|
+
try {
|
|
44
|
+
return await fetch(input, {
|
|
45
|
+
...init,
|
|
46
|
+
signal: init.signal ?? controller.signal,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
if (err?.name === "AbortError") {
|
|
51
|
+
throw new Error(`Request timed out after ${Math.round(timeoutMs / 1000)}s`);
|
|
52
|
+
}
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
clearTimeout(timeout);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function buildApiError(res) {
|
|
60
|
+
const details = await res.json().catch(() => ({ error: res.statusText }));
|
|
61
|
+
const body = details && typeof details === "object" && !Array.isArray(details)
|
|
62
|
+
? details
|
|
63
|
+
: { error: res.statusText };
|
|
64
|
+
return new ApiError(res.status, body, res.statusText);
|
|
65
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ApiError } from "./api.js";
|
|
2
|
+
export function errorPayload(err) {
|
|
3
|
+
if (err instanceof ApiError) {
|
|
4
|
+
return {
|
|
5
|
+
error: err.message,
|
|
6
|
+
...err.details,
|
|
7
|
+
status: err.status,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
return { error: getErrorMessage(err) };
|
|
11
|
+
}
|
|
12
|
+
export function getErrorMessage(err) {
|
|
13
|
+
if (err instanceof Error)
|
|
14
|
+
return err.message;
|
|
15
|
+
return String(err);
|
|
16
|
+
}
|
|
17
|
+
export function printCommandError(prefix, err, json) {
|
|
18
|
+
if (json) {
|
|
19
|
+
console.log(JSON.stringify(errorPayload(err)));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
console.error(`${prefix}: ${getErrorMessage(err)}`);
|
|
23
|
+
}
|
package/dist/commands/create.js
CHANGED
|
@@ -1,18 +1,25 @@
|
|
|
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
5
|
export const createCommand = new Command("create")
|
|
5
6
|
.argument("<name>", "Session name")
|
|
6
7
|
.description("Create a new sharing session")
|
|
7
8
|
.option("--json", "Output JSON")
|
|
8
9
|
.action(async (name, opts) => {
|
|
9
10
|
const config = requireConfig();
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
try {
|
|
12
|
+
const data = await api(config, "POST", "/sessions", { name });
|
|
13
|
+
if (opts.json) {
|
|
14
|
+
console.log(JSON.stringify(data));
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
console.log(`Created session: ${data.id}`);
|
|
18
|
+
console.log(`URL: ${data.url}`);
|
|
19
|
+
}
|
|
13
20
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
catch (err) {
|
|
22
|
+
printCommandError("Create failed", err, opts.json);
|
|
23
|
+
process.exit(1);
|
|
17
24
|
}
|
|
18
25
|
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
export type DoctorStatus = "ok" | "failed" | "skipped";
|
|
3
|
+
export interface DoctorReport {
|
|
4
|
+
cliVersion: string;
|
|
5
|
+
nodeVersion: string;
|
|
6
|
+
configPath: string;
|
|
7
|
+
configured: boolean;
|
|
8
|
+
serverUrl: string | null;
|
|
9
|
+
health: DoctorStatus;
|
|
10
|
+
auth: DoctorStatus;
|
|
11
|
+
activeSessions: number | null;
|
|
12
|
+
maxActiveSessions: number | null;
|
|
13
|
+
activeSessionUnlimited?: boolean;
|
|
14
|
+
testPush: DoctorStatus;
|
|
15
|
+
errors?: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare const doctorCommand: Command;
|
|
18
|
+
export declare function runDoctor(opts?: {
|
|
19
|
+
testPush?: boolean;
|
|
20
|
+
}): Promise<DoctorReport>;
|
|
21
|
+
export declare function formatDoctorReport(report: DoctorReport): string;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { api, fetchWithTimeout } from "../api.js";
|
|
3
|
+
import { getConfigPath, readConfig } from "../config.js";
|
|
4
|
+
import { getErrorMessage } from "../command-error.js";
|
|
5
|
+
import { formatQuota } from "../quota.js";
|
|
6
|
+
import { CLI_VERSION } from "../version.js";
|
|
7
|
+
export const doctorCommand = new Command("doctor")
|
|
8
|
+
.description("Print a support snapshot for debugging Peekable setup")
|
|
9
|
+
.option("--json", "Output JSON")
|
|
10
|
+
.option("--test-push", "Create, push, and close a temporary test session")
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
const report = await runDoctor({ testPush: Boolean(opts.testPush) });
|
|
13
|
+
if (opts.json) {
|
|
14
|
+
console.log(JSON.stringify(report));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
console.log(formatDoctorReport(report));
|
|
18
|
+
});
|
|
19
|
+
export async function runDoctor(opts = {}) {
|
|
20
|
+
const config = readConfig();
|
|
21
|
+
const report = {
|
|
22
|
+
cliVersion: CLI_VERSION,
|
|
23
|
+
nodeVersion: process.version,
|
|
24
|
+
configPath: redactHomePath(getConfigPath()),
|
|
25
|
+
configured: Boolean(config),
|
|
26
|
+
serverUrl: config?.url ?? null,
|
|
27
|
+
health: "skipped",
|
|
28
|
+
auth: "skipped",
|
|
29
|
+
activeSessions: null,
|
|
30
|
+
maxActiveSessions: null,
|
|
31
|
+
activeSessionUnlimited: false,
|
|
32
|
+
testPush: "skipped",
|
|
33
|
+
errors: [],
|
|
34
|
+
};
|
|
35
|
+
if (!config) {
|
|
36
|
+
report.errors.push("Not configured. Run `peekable register` first.");
|
|
37
|
+
return report;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const res = await fetchWithTimeout(`${config.url}/health`);
|
|
41
|
+
report.health = res.ok ? "ok" : "failed";
|
|
42
|
+
if (!res.ok)
|
|
43
|
+
report.errors.push(`Health check failed: HTTP ${res.status}`);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
report.health = "failed";
|
|
47
|
+
report.errors.push(`Health check failed: ${getErrorMessage(err)}`);
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const data = await getUsageSnapshot(config);
|
|
51
|
+
report.auth = "ok";
|
|
52
|
+
report.activeSessions = data.limits?.activeSessions ?? null;
|
|
53
|
+
report.maxActiveSessions = data.limits?.maxActiveSessions ?? null;
|
|
54
|
+
report.activeSessionUnlimited = Boolean(data.limits?.unlimited);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
report.auth = "failed";
|
|
58
|
+
report.errors.push(`Auth check failed: ${getErrorMessage(err)}`);
|
|
59
|
+
}
|
|
60
|
+
if (opts.testPush && report.auth === "ok") {
|
|
61
|
+
try {
|
|
62
|
+
const session = await api(config, "POST", "/sessions", { name: "__peekable_doctor__" });
|
|
63
|
+
try {
|
|
64
|
+
await api(config, "POST", `/sessions/${session.id}/push`, {
|
|
65
|
+
html: "<html><body><p>Peekable doctor test</p></body></html>",
|
|
66
|
+
});
|
|
67
|
+
report.testPush = "ok";
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
await api(config, "DELETE", `/sessions/${session.id}`).catch(() => undefined);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
report.testPush = "failed";
|
|
75
|
+
report.errors.push(`Test push failed: ${getErrorMessage(err)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (report.errors?.length === 0)
|
|
79
|
+
delete report.errors;
|
|
80
|
+
return report;
|
|
81
|
+
}
|
|
82
|
+
export function formatDoctorReport(report) {
|
|
83
|
+
const lines = [
|
|
84
|
+
`Peekable CLI: ${report.cliVersion}`,
|
|
85
|
+
`Node: ${report.nodeVersion}`,
|
|
86
|
+
`Config: ${report.configured ? report.configPath : "missing"}`,
|
|
87
|
+
`Server: ${formatServer(report)}`,
|
|
88
|
+
`Auth: ${formatAuth(report.auth)}`,
|
|
89
|
+
`Active sessions: ${formatQuota(report.activeSessions, report.maxActiveSessions, report.activeSessionUnlimited)}`,
|
|
90
|
+
`Test push: ${report.testPush}`,
|
|
91
|
+
];
|
|
92
|
+
if (report.activeSessions !== null &&
|
|
93
|
+
report.maxActiveSessions !== null &&
|
|
94
|
+
!report.activeSessionUnlimited &&
|
|
95
|
+
report.activeSessions >= report.maxActiveSessions) {
|
|
96
|
+
lines.push("Free plan limit reached. Close one with `peekable close <id>`.");
|
|
97
|
+
}
|
|
98
|
+
if (report.errors?.length) {
|
|
99
|
+
lines.push("", "Errors:");
|
|
100
|
+
for (const error of report.errors)
|
|
101
|
+
lines.push(`- ${error}`);
|
|
102
|
+
}
|
|
103
|
+
return lines.join("\n");
|
|
104
|
+
}
|
|
105
|
+
function formatServer(report) {
|
|
106
|
+
if (!report.serverUrl)
|
|
107
|
+
return "not configured";
|
|
108
|
+
if (report.health === "ok")
|
|
109
|
+
return `${report.serverUrl} reachable`;
|
|
110
|
+
if (report.health === "failed")
|
|
111
|
+
return `${report.serverUrl} unreachable`;
|
|
112
|
+
return `${report.serverUrl} not checked`;
|
|
113
|
+
}
|
|
114
|
+
function formatAuth(status) {
|
|
115
|
+
if (status === "ok")
|
|
116
|
+
return "valid";
|
|
117
|
+
if (status === "failed")
|
|
118
|
+
return "failed";
|
|
119
|
+
return "not checked";
|
|
120
|
+
}
|
|
121
|
+
async function getUsageSnapshot(config) {
|
|
122
|
+
try {
|
|
123
|
+
return await api(config, "GET", "/me/usage");
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
const data = await api(config, "GET", "/sessions");
|
|
127
|
+
return {
|
|
128
|
+
limits: {
|
|
129
|
+
activeSessions: data.limits?.activeSessions ?? data.sessions?.length ?? null,
|
|
130
|
+
maxActiveSessions: data.limits?.maxActiveSessions ?? null,
|
|
131
|
+
unlimited: Boolean(data.limits?.unlimited),
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function redactHomePath(path) {
|
|
137
|
+
const home = process.env.HOME;
|
|
138
|
+
if (!home || !path.startsWith(home))
|
|
139
|
+
return path;
|
|
140
|
+
return `~${path.slice(home.length)}`;
|
|
141
|
+
}
|
|
@@ -7,20 +7,50 @@ export const feedbackCommand = new Command("feedback")
|
|
|
7
7
|
.option("--json", "Output JSON")
|
|
8
8
|
.action(async (sessionId, opts) => {
|
|
9
9
|
const config = requireConfig();
|
|
10
|
-
const
|
|
10
|
+
const [eventsData, annotationsData] = await Promise.all([
|
|
11
|
+
api(config, "GET", `/sessions/${sessionId}/feedback`),
|
|
12
|
+
api(config, "GET", `/sessions/${sessionId}/annotations`).catch(() => ({ annotations: [] })),
|
|
13
|
+
]);
|
|
11
14
|
if (opts.json) {
|
|
12
|
-
console.log(JSON.stringify(
|
|
15
|
+
console.log(JSON.stringify({ ...eventsData, ...annotationsData }));
|
|
13
16
|
return;
|
|
14
17
|
}
|
|
15
|
-
|
|
18
|
+
const hasFeedback = eventsData.feedback.length > 0;
|
|
19
|
+
const hasAnnotations = annotationsData.annotations && annotationsData.annotations.length > 0;
|
|
20
|
+
if (!hasFeedback && !hasAnnotations) {
|
|
16
21
|
console.log("No feedback yet.");
|
|
17
22
|
return;
|
|
18
23
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
console.log(
|
|
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}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Display annotations
|
|
36
|
+
if (hasAnnotations) {
|
|
37
|
+
console.log("\nAnnotations:");
|
|
38
|
+
for (const group of annotationsData.annotations) {
|
|
39
|
+
console.log(`\n--- Version ${group.version} ---`);
|
|
40
|
+
for (const ann of group.items) {
|
|
41
|
+
const who = ann.viewer ?? "Anonymous";
|
|
42
|
+
const elementName = ann.element_name || "Unknown";
|
|
43
|
+
const selector = ann.selector || "";
|
|
44
|
+
console.log(` [${who}] ${elementName} (${selector})`);
|
|
45
|
+
console.log(` "${ann.note}"`);
|
|
46
|
+
if (ann.element_context?.computedStyles) {
|
|
47
|
+
const styles = Object.entries(ann.element_context.computedStyles)
|
|
48
|
+
.map(([key, val]) => `${key}: ${val}`)
|
|
49
|
+
.join(", ");
|
|
50
|
+
if (styles)
|
|
51
|
+
console.log(` Styles: ${styles}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
24
54
|
}
|
|
25
55
|
}
|
|
26
56
|
});
|
package/dist/commands/init.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { existsSync, mkdirSync, cpSync } from "fs";
|
|
3
3
|
import { join, dirname } from "path";
|
|
4
|
-
import { homedir } from "os";
|
|
5
4
|
import { fileURLToPath } from "url";
|
|
6
5
|
import { requireConfig } from "../config.js";
|
|
7
6
|
import { api } from "../api.js";
|
|
7
|
+
import { getClaudeDir, getClaudePeekableSkillDir } from "../paths.js";
|
|
8
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
9
|
export const initCommand = new Command("init")
|
|
10
10
|
.description("Set up peekable — install Claude Code skill and verify connection")
|
|
@@ -31,11 +31,11 @@ export const initCommand = new Command("init")
|
|
|
31
31
|
if (!opts.json)
|
|
32
32
|
console.log("Connection verified.");
|
|
33
33
|
// 2. Detect Claude Code and install skill
|
|
34
|
-
const claudeDir =
|
|
34
|
+
const claudeDir = getClaudeDir();
|
|
35
35
|
const claudeCodeDetected = existsSync(claudeDir);
|
|
36
36
|
result.claude_code = claudeCodeDetected;
|
|
37
37
|
if (claudeCodeDetected) {
|
|
38
|
-
const skillDir =
|
|
38
|
+
const skillDir = getClaudePeekableSkillDir();
|
|
39
39
|
// Resolve skill source relative to compiled output location
|
|
40
40
|
const skillSource = join(__dirname, "..", "..", "skill", "SKILL.md");
|
|
41
41
|
if (existsSync(skillSource)) {
|
package/dist/commands/list.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { requireConfig } from "../config.js";
|
|
3
3
|
import { api } from "../api.js";
|
|
4
|
+
import { formatQuota } from "../quota.js";
|
|
4
5
|
export const listCommand = new Command("list")
|
|
5
6
|
.description("List active sessions")
|
|
6
7
|
.option("--json", "Output JSON")
|
|
@@ -12,10 +13,20 @@ export const listCommand = new Command("list")
|
|
|
12
13
|
return;
|
|
13
14
|
}
|
|
14
15
|
if (data.sessions.length === 0) {
|
|
15
|
-
|
|
16
|
+
if (data.limits) {
|
|
17
|
+
console.log(`No active sessions. (${formatQuota(data.limits.activeSessions, data.limits.maxActiveSessions, data.limits.unlimited)})`);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
console.log("No active sessions.");
|
|
21
|
+
}
|
|
16
22
|
return;
|
|
17
23
|
}
|
|
18
|
-
|
|
24
|
+
if (data.limits) {
|
|
25
|
+
console.log(`Active sessions (${formatQuota(data.limits.activeSessions, data.limits.maxActiveSessions, data.limits.unlimited)}):`);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
console.log("Active sessions:");
|
|
29
|
+
}
|
|
19
30
|
for (const s of data.sessions) {
|
|
20
31
|
console.log(` ${s.id} ${s.name} (${new Date(s.created_at).toLocaleDateString()})`);
|
|
21
32
|
}
|