@peekdev/cli 0.1.0-alpha.1
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/NOTICE +8 -0
- package/dist/commands/audit.d.ts +3 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +96 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +180 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/sessions.d.ts +3 -0
- package/dist/commands/sessions.d.ts.map +1 -0
- package/dist/commands/sessions.js +214 -0
- package/dist/commands/sessions.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +88 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/audit.d.ts +47 -0
- package/dist/lib/audit.d.ts.map +1 -0
- package/dist/lib/audit.js +68 -0
- package/dist/lib/audit.js.map +1 -0
- package/dist/lib/db.d.ts +86 -0
- package/dist/lib/db.d.ts.map +1 -0
- package/dist/lib/db.js +117 -0
- package/dist/lib/db.js.map +1 -0
- package/dist/lib/duration.d.ts +14 -0
- package/dist/lib/duration.d.ts.map +1 -0
- package/dist/lib/duration.js +41 -0
- package/dist/lib/duration.js.map +1 -0
- package/dist/lib/format/index.d.ts +16 -0
- package/dist/lib/format/index.d.ts.map +1 -0
- package/dist/lib/format/index.js +43 -0
- package/dist/lib/format/index.js.map +1 -0
- package/dist/lib/format/json.d.ts +35 -0
- package/dist/lib/format/json.d.ts.map +1 -0
- package/dist/lib/format/json.js +41 -0
- package/dist/lib/format/json.js.map +1 -0
- package/dist/lib/format/markdown.d.ts +4 -0
- package/dist/lib/format/markdown.d.ts.map +1 -0
- package/dist/lib/format/markdown.js +76 -0
- package/dist/lib/format/markdown.js.map +1 -0
- package/dist/lib/fs-atomic.d.ts +10 -0
- package/dist/lib/fs-atomic.d.ts.map +1 -0
- package/dist/lib/fs-atomic.js +32 -0
- package/dist/lib/fs-atomic.js.map +1 -0
- package/dist/lib/init-config.d.ts +81 -0
- package/dist/lib/init-config.d.ts.map +1 -0
- package/dist/lib/init-config.js +152 -0
- package/dist/lib/init-config.js.map +1 -0
- package/dist/lib/output.d.ts +5 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +23 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/peek-home.d.ts +5 -0
- package/dist/lib/peek-home.d.ts.map +1 -0
- package/dist/lib/peek-home.js +11 -0
- package/dist/lib/peek-home.js.map +1 -0
- package/dist/lib/prompt.d.ts +25 -0
- package/dist/lib/prompt.d.ts.map +1 -0
- package/dist/lib/prompt.js +62 -0
- package/dist/lib/prompt.js.map +1 -0
- package/dist/lib/status.d.ts +54 -0
- package/dist/lib/status.d.ts.map +1 -0
- package/dist/lib/status.js +62 -0
- package/dist/lib/status.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +6 -0
- package/dist/version.js.map +1 -0
- package/package.json +42 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// `--format json`: machine-readable export. Same schema the MCP tools return
|
|
2
|
+
// (P2 PRD §C.3 "same schema as MCP tool returns", §B3) so an AI consumer can
|
|
3
|
+
// treat a CLI export and an MCP `get_session_*` response interchangeably. Pure:
|
|
4
|
+
// SessionDetail in, JSON string out.
|
|
5
|
+
/** Build the JSON export object (pure; not yet stringified). */
|
|
6
|
+
export function toJsonExport(detail) {
|
|
7
|
+
const { session, counts, consoleErrors, networkErrors } = detail;
|
|
8
|
+
return {
|
|
9
|
+
id: session.id,
|
|
10
|
+
origin: session.origin,
|
|
11
|
+
url: session.url,
|
|
12
|
+
title: session.title,
|
|
13
|
+
startedAt: session.createdAt,
|
|
14
|
+
updatedAt: session.updatedAt,
|
|
15
|
+
status: session.status,
|
|
16
|
+
eventCount: session.eventCount,
|
|
17
|
+
bytes: session.bytes,
|
|
18
|
+
errorCount: counts.consoleErrors + counts.networkErrors,
|
|
19
|
+
consoleErrors: consoleErrors.map((c) => ({
|
|
20
|
+
ts: c.ts,
|
|
21
|
+
level: c.level,
|
|
22
|
+
message: c.message,
|
|
23
|
+
stack: c.stack,
|
|
24
|
+
})),
|
|
25
|
+
networkErrors: networkErrors.map((n) => ({
|
|
26
|
+
ts: n.ts,
|
|
27
|
+
method: n.method,
|
|
28
|
+
url: n.url,
|
|
29
|
+
status: n.status,
|
|
30
|
+
statusText: n.statusText,
|
|
31
|
+
resourceType: n.resourceType,
|
|
32
|
+
durationMs: n.durationMs,
|
|
33
|
+
errorText: n.errorText,
|
|
34
|
+
})),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Render a session as a pretty-printed JSON string (2-space indent). */
|
|
38
|
+
export function formatSessionJson(detail) {
|
|
39
|
+
return `${JSON.stringify(toJsonExport(detail), null, 2)}\n`;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../../src/lib/format/json.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,6EAA6E;AAC7E,gFAAgF;AAChF,qCAAqC;AAkCrC,gEAAgE;AAChE,MAAM,UAAU,YAAY,CAAC,MAAqB;IAChD,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC;IACjE,OAAO;QACL,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,UAAU,EAAE,MAAM,CAAC,aAAa,GAAG,MAAM,CAAC,aAAa;QACvD,aAAa,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACvC,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,OAAO,EAAE,CAAC,CAAC,OAAO;YAClB,KAAK,EAAE,CAAC,CAAC,KAAK;SACf,CAAC,CAAC;QACH,aAAa,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACvC,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,GAAG,EAAE,CAAC,CAAC,GAAG;YACV,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,YAAY,EAAE,CAAC,CAAC,YAAY;YAC5B,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,SAAS,EAAE,CAAC,CAAC,SAAS;SACvB,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,iBAAiB,CAAC,MAAqB;IACrD,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../../src/lib/format/markdown.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAoC,aAAa,EAAE,MAAM,UAAU,CAAC;AAgChF,6DAA6D;AAC7D,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CA0CnE"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// `--format markdown`: structured for AI paste, mirroring Playwright's "Copy
|
|
2
|
+
// Prompt" convention (P2 PRD §C.3). Section order is fixed:
|
|
3
|
+
// ## Page
|
|
4
|
+
// ## Console errors
|
|
5
|
+
// ## Failed requests
|
|
6
|
+
// ## User actions before error
|
|
7
|
+
// ## Suggested reproduction
|
|
8
|
+
// Pure: SessionDetail in, Markdown string out.
|
|
9
|
+
//
|
|
10
|
+
// Note on the last two sections: per-event user actions live in the gzipped
|
|
11
|
+
// rrweb blob, surfaced by the MCP `get_user_action_before_error` /
|
|
12
|
+
// `generate_playwright_repro` tools (Phase 3c). The CLI markdown export reads
|
|
13
|
+
// only the extracted SQL rows, so it states where the richer data lives rather
|
|
14
|
+
// than fabricating it.
|
|
15
|
+
function isoFromMs(tsMs) {
|
|
16
|
+
return new Date(tsMs).toISOString();
|
|
17
|
+
}
|
|
18
|
+
function consoleSection(rows) {
|
|
19
|
+
if (rows.length === 0)
|
|
20
|
+
return 'No console errors recorded.';
|
|
21
|
+
return rows
|
|
22
|
+
.map((r) => {
|
|
23
|
+
const head = `- \`${r.level}\` ${isoFromMs(r.ts)} — ${r.message}`;
|
|
24
|
+
if (!r.stack)
|
|
25
|
+
return head;
|
|
26
|
+
const stack = r.stack
|
|
27
|
+
.split('\n')
|
|
28
|
+
.map((line) => ` ${line}`)
|
|
29
|
+
.join('\n');
|
|
30
|
+
return `${head}\n${stack}`;
|
|
31
|
+
})
|
|
32
|
+
.join('\n');
|
|
33
|
+
}
|
|
34
|
+
function networkSection(rows) {
|
|
35
|
+
if (rows.length === 0)
|
|
36
|
+
return 'No failed requests recorded.';
|
|
37
|
+
return rows
|
|
38
|
+
.map((r) => {
|
|
39
|
+
const status = r.status ?? (r.errorText ? `ERR ${r.errorText}` : 'pending');
|
|
40
|
+
const dur = r.durationMs != null ? ` (${r.durationMs}ms)` : '';
|
|
41
|
+
return `- ${r.method} ${r.url} → ${status}${dur}`;
|
|
42
|
+
})
|
|
43
|
+
.join('\n');
|
|
44
|
+
}
|
|
45
|
+
/** Render a session as the §C.3 Markdown AI-paste format. */
|
|
46
|
+
export function formatSessionMarkdown(detail) {
|
|
47
|
+
const { session, counts, consoleErrors, networkErrors } = detail;
|
|
48
|
+
const title = session.title ?? '(untitled session)';
|
|
49
|
+
const lines = [];
|
|
50
|
+
lines.push(`# Peek session ${session.id}`);
|
|
51
|
+
lines.push('');
|
|
52
|
+
lines.push('## Page');
|
|
53
|
+
lines.push(`- Title: ${title}`);
|
|
54
|
+
lines.push(`- URL: ${session.url ?? '(unknown)'}`);
|
|
55
|
+
lines.push(`- Origin: ${session.origin ?? '(unknown)'}`);
|
|
56
|
+
lines.push(`- Started: ${session.createdAt}`);
|
|
57
|
+
lines.push(`- Updated: ${session.updatedAt}`);
|
|
58
|
+
lines.push(`- Status: ${session.status}`);
|
|
59
|
+
lines.push(`- Events: ${session.eventCount} · Console errors: ${counts.consoleErrors} · Failed requests: ${counts.networkErrors}`);
|
|
60
|
+
lines.push('');
|
|
61
|
+
lines.push('## Console errors');
|
|
62
|
+
lines.push(consoleSection(consoleErrors));
|
|
63
|
+
lines.push('');
|
|
64
|
+
lines.push('## Failed requests');
|
|
65
|
+
lines.push(networkSection(networkErrors));
|
|
66
|
+
lines.push('');
|
|
67
|
+
lines.push('## User actions before error');
|
|
68
|
+
lines.push('User-action timeline lives in the recorded rrweb stream. Use the MCP tool ' +
|
|
69
|
+
'`get_user_action_before_error` (peek-mcp) for the click/type/navigation sequence.');
|
|
70
|
+
lines.push('');
|
|
71
|
+
lines.push('## Suggested reproduction');
|
|
72
|
+
lines.push(`Generate a Playwright repro with \`peek sessions export ${session.id} --format playwright\`, or the MCP \`generate_playwright_repro\` tool.`);
|
|
73
|
+
lines.push('');
|
|
74
|
+
return lines.join('\n');
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../../src/lib/format/markdown.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write `content` to `path` atomically: write a temp file in the SAME directory
|
|
3
|
+
* then `renameSync` over the target (rename is atomic on a single filesystem).
|
|
4
|
+
* This avoids the truncate-then-write window of a plain `writeFileSync` that a
|
|
5
|
+
* crash / full disk / OOM could leave as an empty or partial file — and
|
|
6
|
+
* `~/.claude.json` is read on every Claude Code startup. Parent dirs are
|
|
7
|
+
* created; on failure the temp file is best-effort removed.
|
|
8
|
+
*/
|
|
9
|
+
export declare function atomicWriteFileSync(path: string, content: string): void;
|
|
10
|
+
//# sourceMappingURL=fs-atomic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-atomic.d.ts","sourceRoot":"","sources":["../../src/lib/fs-atomic.ts"],"names":[],"mappings":"AAQA;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAcvE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Atomic file write used by `peek init` when rewriting a user's MCP-client
|
|
2
|
+
// config (~/.claude.json etc.). Factored out of the command shell so the
|
|
3
|
+
// crash-safety behavior is directly testable.
|
|
4
|
+
import { randomBytes } from 'node:crypto';
|
|
5
|
+
import { mkdirSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { dirname } from 'node:path';
|
|
7
|
+
/**
|
|
8
|
+
* Write `content` to `path` atomically: write a temp file in the SAME directory
|
|
9
|
+
* then `renameSync` over the target (rename is atomic on a single filesystem).
|
|
10
|
+
* This avoids the truncate-then-write window of a plain `writeFileSync` that a
|
|
11
|
+
* crash / full disk / OOM could leave as an empty or partial file — and
|
|
12
|
+
* `~/.claude.json` is read on every Claude Code startup. Parent dirs are
|
|
13
|
+
* created; on failure the temp file is best-effort removed.
|
|
14
|
+
*/
|
|
15
|
+
export function atomicWriteFileSync(path, content) {
|
|
16
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
17
|
+
const tmp = `${path}.peek-tmp-${randomBytes(4).toString('hex')}`;
|
|
18
|
+
try {
|
|
19
|
+
writeFileSync(tmp, content, 'utf8');
|
|
20
|
+
renameSync(tmp, path);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
try {
|
|
24
|
+
unlinkSync(tmp);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// temp file may not exist (writeFileSync failed before creating it).
|
|
28
|
+
}
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=fs-atomic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-atomic.js","sourceRoot":"","sources":["../../src/lib/fs-atomic.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,yEAAyE;AACzE,8CAA8C;AAE9C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,OAAe;IAC/D,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,GAAG,GAAG,GAAG,IAAI,aAAa,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;IACjE,IAAI,CAAC;QACH,aAAa,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACpC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACxB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC;YACH,UAAU,CAAC,GAAG,CAAC,CAAC;QAClB,CAAC;QAAC,MAAM,CAAC;YACP,qEAAqE;QACvE,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** The MCP server key peek registers under, and the block written for it. */
|
|
2
|
+
export declare const PEEK_SERVER_KEY = "peek";
|
|
3
|
+
/** The `mcpServers.peek` block (P2 PRD §K.1-K.5): `npx -y @peekdev/mcp`. */
|
|
4
|
+
export interface PeekMcpServerBlock {
|
|
5
|
+
readonly command: 'npx';
|
|
6
|
+
readonly args: readonly ['-y', '@peekdev/mcp'];
|
|
7
|
+
}
|
|
8
|
+
export declare const PEEK_MCP_BLOCK: PeekMcpServerBlock;
|
|
9
|
+
/** Stable identifiers for the supported MCP clients. */
|
|
10
|
+
export type ClientId = 'claude-code' | 'cursor' | 'vscode' | 'windsurf' | 'cline';
|
|
11
|
+
/** Static metadata about one MCP client's config file. */
|
|
12
|
+
export interface ClientDescriptor {
|
|
13
|
+
readonly id: ClientId;
|
|
14
|
+
/** Human label shown in the wizard (P2 PRD §K.5). */
|
|
15
|
+
readonly label: string;
|
|
16
|
+
/**
|
|
17
|
+
* Where the config file is resolved. `home`-relative for the per-user
|
|
18
|
+
* clients; `cwd`-relative for the project-scoped ones (VS Code's
|
|
19
|
+
* `.vscode/mcp.json`).
|
|
20
|
+
*/
|
|
21
|
+
readonly scope: 'home' | 'project';
|
|
22
|
+
/** Path segments under the scope root, e.g. ['.cursor', 'mcp.json']. */
|
|
23
|
+
readonly pathSegments: readonly string[];
|
|
24
|
+
/**
|
|
25
|
+
* Cline stores its MCP config inside the VS Code extension's globalStorage
|
|
26
|
+
* under an OS-specific path the CLI can't reliably resolve, so the wizard
|
|
27
|
+
* marks it "manual config required" rather than writing blindly (matches the
|
|
28
|
+
* §K.5 transcript). `true` ⇒ detection/merge are advisory only.
|
|
29
|
+
*/
|
|
30
|
+
readonly manualOnly?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The five MCP clients from the §K.5 wizard. Cline is `manualOnly` because its
|
|
34
|
+
* `cline_mcp_settings.json` lives in VS Code's per-OS globalStorage (the §K.5
|
|
35
|
+
* transcript shows "manual config required" for it).
|
|
36
|
+
*/
|
|
37
|
+
export declare const CLIENTS: readonly ClientDescriptor[];
|
|
38
|
+
/** Resolve a client's absolute config path given the home dir and cwd. */
|
|
39
|
+
export declare function clientConfigPath(client: ClientDescriptor, homeDir: string, cwd: string): string;
|
|
40
|
+
/** A client paired with its resolved path and whether the file exists. */
|
|
41
|
+
export interface DetectedClient extends ClientDescriptor {
|
|
42
|
+
readonly configPath: string;
|
|
43
|
+
/** True if the config file already exists on disk. */
|
|
44
|
+
readonly exists: boolean;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Detect which clients have a config present. `fileExists` is injected (the
|
|
48
|
+
* shell passes a real `existsSync`; tests pass a fake) so detection stays pure.
|
|
49
|
+
* Every client is returned with its resolved path + `exists`; the wizard shows
|
|
50
|
+
* detected ones and lets the user pick.
|
|
51
|
+
*/
|
|
52
|
+
export declare function detectClients(homeDir: string, cwd: string, fileExists: (path: string) => boolean): DetectedClient[];
|
|
53
|
+
/**
|
|
54
|
+
* Merge the peek MCP block into an existing (possibly undefined) parsed config,
|
|
55
|
+
* returning a NEW object (never mutating the input). Any pre-existing
|
|
56
|
+
* `mcpServers` entries are preserved; only the `peek` key is set/overwritten.
|
|
57
|
+
* Non-`mcpServers` top-level keys (e.g. a user's other Claude Code settings)
|
|
58
|
+
* are carried through untouched.
|
|
59
|
+
*
|
|
60
|
+
* Throws if `existing` is present but its `mcpServers` is a non-object (a
|
|
61
|
+
* malformed config the user must fix by hand, rather than us clobbering it).
|
|
62
|
+
*/
|
|
63
|
+
export declare function mergePeekConfig(existing: unknown): Record<string, unknown>;
|
|
64
|
+
/** True if a parsed config already registers a `peek` MCP server. */
|
|
65
|
+
export declare function hasPeekServer(existing: unknown): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Detect line (`//`) or block (slash-star) comments OUTSIDE string literals —
|
|
68
|
+
* i.e. JSONC. VS Code's `.vscode/mcp.json` is JSONC and `JSON.parse` chokes on
|
|
69
|
+
* comments, so the shell uses this to emit an actionable message instead of a
|
|
70
|
+
* cryptic "Unexpected token /". String contents are skipped so a value like
|
|
71
|
+
* `"https://..."` does NOT count as a comment.
|
|
72
|
+
*/
|
|
73
|
+
export declare function containsJsonComments(raw: string): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* The `mcpServers.peek` block as a pretty-printed JSON snippet, for the
|
|
76
|
+
* "add this manually" message path (e.g. JSONC configs we won't rewrite).
|
|
77
|
+
*/
|
|
78
|
+
export declare const PEEK_BLOCK_SNIPPET: string;
|
|
79
|
+
/** Serialize a merged config for writing (2-space indent + trailing newline). */
|
|
80
|
+
export declare function serializeConfig(config: Record<string, unknown>): string;
|
|
81
|
+
//# sourceMappingURL=init-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-config.d.ts","sourceRoot":"","sources":["../../src/lib/init-config.ts"],"names":[],"mappings":"AASA,6EAA6E;AAC7E,eAAO,MAAM,eAAe,SAAS,CAAC;AAEtC,4EAA4E;AAC5E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;CAChD;AAED,eAAO,MAAM,cAAc,EAAE,kBAG5B,CAAC;AAEF,wDAAwD;AACxD,MAAM,MAAM,QAAQ,GAAG,aAAa,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAElF,0DAA0D;AAC1D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB,qDAAqD;IACrD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,wEAAwE;IACxE,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED;;;;GAIG;AACH,eAAO,MAAM,OAAO,EAAE,SAAS,gBAAgB,EAgC9C,CAAC;AAEF,0EAA0E;AAC1E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAG/F;AAED,0EAA0E;AAC1E,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACtD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,sDAAsD;IACtD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GACpC,cAAc,EAAE,CAKlB;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA2B1E;AAED,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAKxD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAmBzD;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAIhC,CAAC;AAEF,iFAAiF;AACjF,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAEvE"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// `peek init` pure core (P2 PRD §K.5): detect MCP-capable clients and compute
|
|
2
|
+
// the merged config to write. Everything here is a pure function of (home dir,
|
|
3
|
+
// cwd, which clients, possibly-existing config) → result. The interactive
|
|
4
|
+
// prompts and the real `fs` reads/writes live in the command shell
|
|
5
|
+
// (commands/init.ts), so this logic is exhaustively unit-testable against
|
|
6
|
+
// fixtures with pre-existing servers.
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
/** The MCP server key peek registers under, and the block written for it. */
|
|
9
|
+
export const PEEK_SERVER_KEY = 'peek';
|
|
10
|
+
export const PEEK_MCP_BLOCK = {
|
|
11
|
+
command: 'npx',
|
|
12
|
+
args: ['-y', '@peekdev/mcp'],
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The five MCP clients from the §K.5 wizard. Cline is `manualOnly` because its
|
|
16
|
+
* `cline_mcp_settings.json` lives in VS Code's per-OS globalStorage (the §K.5
|
|
17
|
+
* transcript shows "manual config required" for it).
|
|
18
|
+
*/
|
|
19
|
+
export const CLIENTS = [
|
|
20
|
+
{
|
|
21
|
+
id: 'claude-code',
|
|
22
|
+
label: 'Claude Code',
|
|
23
|
+
scope: 'home',
|
|
24
|
+
pathSegments: ['.claude.json'],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'cursor',
|
|
28
|
+
label: 'Cursor',
|
|
29
|
+
scope: 'home',
|
|
30
|
+
pathSegments: ['.cursor', 'mcp.json'],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'vscode',
|
|
34
|
+
label: 'VS Code',
|
|
35
|
+
scope: 'project',
|
|
36
|
+
pathSegments: ['.vscode', 'mcp.json'],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'windsurf',
|
|
40
|
+
label: 'Windsurf',
|
|
41
|
+
scope: 'home',
|
|
42
|
+
pathSegments: ['.codeium', 'windsurf', 'mcp_config.json'],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'cline',
|
|
46
|
+
label: 'Cline',
|
|
47
|
+
scope: 'home',
|
|
48
|
+
pathSegments: ['cline_mcp_settings.json'],
|
|
49
|
+
manualOnly: true,
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
/** Resolve a client's absolute config path given the home dir and cwd. */
|
|
53
|
+
export function clientConfigPath(client, homeDir, cwd) {
|
|
54
|
+
const root = client.scope === 'home' ? homeDir : cwd;
|
|
55
|
+
return join(root, ...client.pathSegments);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Detect which clients have a config present. `fileExists` is injected (the
|
|
59
|
+
* shell passes a real `existsSync`; tests pass a fake) so detection stays pure.
|
|
60
|
+
* Every client is returned with its resolved path + `exists`; the wizard shows
|
|
61
|
+
* detected ones and lets the user pick.
|
|
62
|
+
*/
|
|
63
|
+
export function detectClients(homeDir, cwd, fileExists) {
|
|
64
|
+
return CLIENTS.map((c) => {
|
|
65
|
+
const configPath = clientConfigPath(c, homeDir, cwd);
|
|
66
|
+
return { ...c, configPath, exists: fileExists(configPath) };
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Merge the peek MCP block into an existing (possibly undefined) parsed config,
|
|
71
|
+
* returning a NEW object (never mutating the input). Any pre-existing
|
|
72
|
+
* `mcpServers` entries are preserved; only the `peek` key is set/overwritten.
|
|
73
|
+
* Non-`mcpServers` top-level keys (e.g. a user's other Claude Code settings)
|
|
74
|
+
* are carried through untouched.
|
|
75
|
+
*
|
|
76
|
+
* Throws if `existing` is present but its `mcpServers` is a non-object (a
|
|
77
|
+
* malformed config the user must fix by hand, rather than us clobbering it).
|
|
78
|
+
*/
|
|
79
|
+
export function mergePeekConfig(existing) {
|
|
80
|
+
if (existing === undefined || existing === null) {
|
|
81
|
+
return {
|
|
82
|
+
mcpServers: { [PEEK_SERVER_KEY]: { ...PEEK_MCP_BLOCK, args: [...PEEK_MCP_BLOCK.args] } },
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (typeof existing !== 'object' || Array.isArray(existing)) {
|
|
86
|
+
throw new Error('existing config is not a JSON object');
|
|
87
|
+
}
|
|
88
|
+
const base = existing;
|
|
89
|
+
const existingServers = base.mcpServers;
|
|
90
|
+
if (existingServers !== undefined &&
|
|
91
|
+
(typeof existingServers !== 'object' ||
|
|
92
|
+
existingServers === null ||
|
|
93
|
+
Array.isArray(existingServers))) {
|
|
94
|
+
throw new Error('existing config has a non-object "mcpServers" — refusing to clobber it');
|
|
95
|
+
}
|
|
96
|
+
const servers = existingServers ?? {};
|
|
97
|
+
return {
|
|
98
|
+
...base,
|
|
99
|
+
mcpServers: {
|
|
100
|
+
...servers,
|
|
101
|
+
[PEEK_SERVER_KEY]: { ...PEEK_MCP_BLOCK, args: [...PEEK_MCP_BLOCK.args] },
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** True if a parsed config already registers a `peek` MCP server. */
|
|
106
|
+
export function hasPeekServer(existing) {
|
|
107
|
+
if (typeof existing !== 'object' || existing === null || Array.isArray(existing))
|
|
108
|
+
return false;
|
|
109
|
+
const servers = existing.mcpServers;
|
|
110
|
+
if (typeof servers !== 'object' || servers === null || Array.isArray(servers))
|
|
111
|
+
return false;
|
|
112
|
+
return PEEK_SERVER_KEY in servers;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Detect line (`//`) or block (slash-star) comments OUTSIDE string literals —
|
|
116
|
+
* i.e. JSONC. VS Code's `.vscode/mcp.json` is JSONC and `JSON.parse` chokes on
|
|
117
|
+
* comments, so the shell uses this to emit an actionable message instead of a
|
|
118
|
+
* cryptic "Unexpected token /". String contents are skipped so a value like
|
|
119
|
+
* `"https://..."` does NOT count as a comment.
|
|
120
|
+
*/
|
|
121
|
+
export function containsJsonComments(raw) {
|
|
122
|
+
let inString = false;
|
|
123
|
+
for (let i = 0; i < raw.length; i++) {
|
|
124
|
+
const ch = raw[i];
|
|
125
|
+
if (inString) {
|
|
126
|
+
if (ch === '\\') {
|
|
127
|
+
i++; // skip the escaped char
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (ch === '"')
|
|
131
|
+
inString = false;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (ch === '"') {
|
|
135
|
+
inString = true;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (ch === '/' && (raw[i + 1] === '/' || raw[i + 1] === '*'))
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The `mcpServers.peek` block as a pretty-printed JSON snippet, for the
|
|
145
|
+
* "add this manually" message path (e.g. JSONC configs we won't rewrite).
|
|
146
|
+
*/
|
|
147
|
+
export const PEEK_BLOCK_SNIPPET = JSON.stringify({ mcpServers: { [PEEK_SERVER_KEY]: { ...PEEK_MCP_BLOCK, args: [...PEEK_MCP_BLOCK.args] } } }, null, 2);
|
|
148
|
+
/** Serialize a merged config for writing (2-space indent + trailing newline). */
|
|
149
|
+
export function serializeConfig(config) {
|
|
150
|
+
return `${JSON.stringify(config, null, 2)}\n`;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=init-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-config.js","sourceRoot":"","sources":["../../src/lib/init-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Human-readable byte size (1536 → "1.5 KB"). Binary (1024) units. */
|
|
2
|
+
export declare function formatBytes(bytes: number): string;
|
|
3
|
+
/** Right-pad a string to `width` for simple column alignment. */
|
|
4
|
+
export declare function pad(value: string, width: number): string;
|
|
5
|
+
//# sourceMappingURL=output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/lib/output.ts"],"names":[],"mappings":"AAGA,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAYjD;AAED,iEAAiE;AACjE,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAExD"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Small pure presentation helpers shared by the command shells. Kept here (and
|
|
2
|
+
// unit-tested) so the commands themselves stay a thin print layer.
|
|
3
|
+
/** Human-readable byte size (1536 → "1.5 KB"). Binary (1024) units. */
|
|
4
|
+
export function formatBytes(bytes) {
|
|
5
|
+
if (!Number.isFinite(bytes) || bytes < 0)
|
|
6
|
+
return '0 B';
|
|
7
|
+
if (bytes < 1024)
|
|
8
|
+
return `${bytes} B`;
|
|
9
|
+
const units = ['KB', 'MB', 'GB', 'TB'];
|
|
10
|
+
let value = bytes / 1024;
|
|
11
|
+
let unit = 0;
|
|
12
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
13
|
+
value /= 1024;
|
|
14
|
+
unit++;
|
|
15
|
+
}
|
|
16
|
+
const rounded = value >= 100 ? Math.round(value) : Math.round(value * 10) / 10;
|
|
17
|
+
return `${rounded} ${units[unit]}`;
|
|
18
|
+
}
|
|
19
|
+
/** Right-pad a string to `width` for simple column alignment. */
|
|
20
|
+
export function pad(value, width) {
|
|
21
|
+
return value.length >= width ? value : value + ' '.repeat(width - value.length);
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/lib/output.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AAEnE,uEAAuE;AACvE,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACvD,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,KAAK,IAAI,CAAC;IACtC,MAAM,KAAK,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACvC,IAAI,KAAK,GAAG,KAAK,GAAG,IAAI,CAAC;IACzB,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,OAAO,KAAK,IAAI,IAAI,IAAI,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChD,KAAK,IAAI,IAAI,CAAC;QACd,IAAI,EAAE,CAAC;IACT,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;IAC/E,OAAO,GAAG,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;AACrC,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,GAAG,CAAC,KAAa,EAAE,KAAa;IAC9C,OAAO,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;AAClF,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { defaultDbPath, peekHomeDir } from '@peekdev/mcp/db';
|
|
2
|
+
export { defaultDbPath, peekHomeDir };
|
|
3
|
+
/** Absolute path to the append-only audit log (~/.peek/audit.log, ADR-0010). */
|
|
4
|
+
export declare function auditLogPath(): string;
|
|
5
|
+
//# sourceMappingURL=peek-home.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"peek-home.d.ts","sourceRoot":"","sources":["../../src/lib/peek-home.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE7D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC;AAEtC,gFAAgF;AAChF,wBAAgB,YAAY,IAAI,MAAM,CAErC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Resolve the ~/.peek paths the CLI reads (ADR-0007 layout). The DB path +
|
|
2
|
+
// home come from @peekdev/mcp/db so the CLI and native host agree on the
|
|
3
|
+
// PEEK_HOME override; the audit log path is added here.
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { defaultDbPath, peekHomeDir } from '@peekdev/mcp/db';
|
|
6
|
+
export { defaultDbPath, peekHomeDir };
|
|
7
|
+
/** Absolute path to the append-only audit log (~/.peek/audit.log, ADR-0010). */
|
|
8
|
+
export function auditLogPath() {
|
|
9
|
+
return join(peekHomeDir(), 'audit.log');
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=peek-home.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"peek-home.js","sourceRoot":"","sources":["../../src/lib/peek-home.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,yEAAyE;AACzE,wDAAwD;AAExD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE7D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC;AAEtC,gFAAgF;AAChF,MAAM,UAAU,YAAY;IAC1B,OAAO,IAAI,CAAC,WAAW,EAAE,EAAE,WAAW,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One selectable option. The optional fields explicitly allow `undefined` so
|
|
3
|
+
* callers can build choices inline (e.g. `hint: maybeUndefined`) under
|
|
4
|
+
* `exactOptionalPropertyTypes`.
|
|
5
|
+
*/
|
|
6
|
+
export interface Choice<T> {
|
|
7
|
+
readonly value: T;
|
|
8
|
+
readonly label: string;
|
|
9
|
+
/** Pre-checked in the multi-select default. */
|
|
10
|
+
readonly checked?: boolean | undefined;
|
|
11
|
+
/** Shown but not selectable (e.g. Cline "manual config required"). */
|
|
12
|
+
readonly disabled?: boolean | undefined;
|
|
13
|
+
/** Optional dim hint after the label. */
|
|
14
|
+
readonly hint?: string | undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Numbered multi-select. Prints each choice with its current [x]/[ ] state and
|
|
18
|
+
* reads a comma/space-separated list of numbers to toggle the default. Empty
|
|
19
|
+
* input keeps the defaults. Disabled choices can't be selected. Returns the
|
|
20
|
+
* chosen values in declaration order.
|
|
21
|
+
*/
|
|
22
|
+
export declare function multiSelect<T>(message: string, choices: Choice<T>[]): Promise<T[]>;
|
|
23
|
+
/** Yes/no confirm. `defaultYes` controls the [Y/n] vs [y/N] default on empty input. */
|
|
24
|
+
export declare function confirm(message: string, defaultYes?: boolean): Promise<boolean>;
|
|
25
|
+
//# sourceMappingURL=prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/lib/prompt.ts"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;IAClB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,+CAA+C;IAC/C,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACxC,yCAAyC;IACzC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC;AAMD;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAkCxF;AAED,uFAAuF;AACvF,wBAAsB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,UAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAUlF"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Thin interactive-prompt shell for `peek init`. Deliberately tiny + dependency
|
|
2
|
+
// free (a numbered multi-select over node:readline) so the wizard's PURE logic
|
|
3
|
+
// (detection + config merge in init-config.ts) carries the testable weight and
|
|
4
|
+
// this file stays an I/O edge that's exercised manually. Not unit-tested.
|
|
5
|
+
import { createInterface } from 'node:readline';
|
|
6
|
+
function ask(rl, question) {
|
|
7
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Numbered multi-select. Prints each choice with its current [x]/[ ] state and
|
|
11
|
+
* reads a comma/space-separated list of numbers to toggle the default. Empty
|
|
12
|
+
* input keeps the defaults. Disabled choices can't be selected. Returns the
|
|
13
|
+
* chosen values in declaration order.
|
|
14
|
+
*/
|
|
15
|
+
export async function multiSelect(message, choices) {
|
|
16
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
17
|
+
try {
|
|
18
|
+
const selectable = choices.filter((c) => !c.disabled);
|
|
19
|
+
const checked = new Set(selectable.filter((c) => c.checked).map((c) => c.value));
|
|
20
|
+
process.stdout.write(`${message}\n`);
|
|
21
|
+
choices.forEach((c, i) => {
|
|
22
|
+
const num = c.disabled ? ' ' : String(i + 1).padStart(2, ' ');
|
|
23
|
+
const box = c.disabled ? ' ' : checked.has(c.value) ? '[x]' : '[ ]';
|
|
24
|
+
const hint = c.hint ? ` (${c.hint})` : '';
|
|
25
|
+
const dim = c.disabled ? ' — manual config required' : '';
|
|
26
|
+
process.stdout.write(` ${num} ${box} ${c.label}${hint}${dim}\n`);
|
|
27
|
+
});
|
|
28
|
+
const answer = (await ask(rl, 'Toggle by number (comma/space separated), Enter to accept defaults: ')).trim();
|
|
29
|
+
if (answer.length > 0) {
|
|
30
|
+
const tokens = answer.split(/[\s,]+/).filter(Boolean);
|
|
31
|
+
for (const tok of tokens) {
|
|
32
|
+
const idx = Number(tok) - 1;
|
|
33
|
+
const choice = choices[idx];
|
|
34
|
+
if (!choice || choice.disabled)
|
|
35
|
+
continue;
|
|
36
|
+
if (checked.has(choice.value))
|
|
37
|
+
checked.delete(choice.value);
|
|
38
|
+
else
|
|
39
|
+
checked.add(choice.value);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return choices.filter((c) => !c.disabled && checked.has(c.value)).map((c) => c.value);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
rl.close();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Yes/no confirm. `defaultYes` controls the [Y/n] vs [y/N] default on empty input. */
|
|
49
|
+
export async function confirm(message, defaultYes = true) {
|
|
50
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
51
|
+
try {
|
|
52
|
+
const suffix = defaultYes ? '[Y/n]' : '[y/N]';
|
|
53
|
+
const answer = (await ask(rl, `${message} ${suffix} `)).trim().toLowerCase();
|
|
54
|
+
if (answer.length === 0)
|
|
55
|
+
return defaultYes;
|
|
56
|
+
return answer === 'y' || answer === 'yes';
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
rl.close();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/lib/prompt.ts"],"names":[],"mappings":"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"}
|