promptdock 0.1.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/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # promptdock
2
+
3
+ Install AI agent **skills** from [PromptDock](https://promptdock.ai) into Claude Code,
4
+ Codex, Gemini CLI, Cursor, GitHub Copilot, and more.
5
+
6
+ ```bash
7
+ npx promptdock@latest install <handle>/<slug>
8
+ ```
9
+
10
+ ## 60-second quickstart
11
+
12
+ ```bash
13
+ # 1) Install a skill into this project (the CLI walks you through login + target)
14
+ npx promptdock@latest install garry/seo-content-audit
15
+
16
+ # 2) Approve the login code in your browser when prompted (one time per machine)
17
+
18
+ # 3) Start a NEW session in Claude Code and type /seo-content-audit
19
+ ```
20
+
21
+ **Auth model, stated plainly: login is required.** A free PromptDock account
22
+ installs free skills — no card, no subscription. Premium skills need the
23
+ Explorer plan ($10/mo) or higher. `login` opens your browser; you approve a
24
+ one-time code and the terminal continues. The token lives in
25
+ `~/.promptdock/config.json` (owner-only permissions; on Windows the user
26
+ profile's ACL is the boundary).
27
+
28
+ ## Commands
29
+
30
+ | Command | What it does |
31
+ | --- | --- |
32
+ | `login [--token pdk_…]` | Browser hand-off login, or store a CI token from Settings → CLI sessions |
33
+ | `logout` | Remove the local token (revoke in Settings to kill the session server-side) |
34
+ | `whoami [--json]` | Show the signed-in account + token expiry |
35
+ | `install <ref> [options]` | Install a skill (`ref` = `handle/slug`, `@handle/slug`, or a pasted URL) |
36
+ | `uninstall <ref \| --all>` | Remove an installed skill (receipt-driven — see below) |
37
+ | `update [ref] [--all] [--check]` | Update to the latest approved version (`upgrade` is an alias) |
38
+ | `list [--json] [-g] [--all]` | Show installed skills |
39
+
40
+ Install options: `-g/--global` (per-user dir), `--target <tool>`, `--dir <path>`,
41
+ `-y/--yes` (skip prompts — **never** implies `--force`), `--force`, `--dry-run`
42
+ (print the plan, install nothing), `--json`.
43
+
44
+ ## Install targets
45
+
46
+ | Target | Project install | Global (`-g`) | Status |
47
+ | --- | --- | --- | --- |
48
+ | `claude` (Claude Code) | `.claude/skills/<slug>/` | `~/.claude/skills/<slug>/` | supported |
49
+ | `codex` | `.codex/skills/<slug>/` | `~/.codex/skills/<slug>/` | experimental |
50
+ | `gemini` (Gemini CLI) | `.gemini/skills/<slug>/` | `~/.gemini/skills/<slug>/` | experimental |
51
+ | `cursor` | `.cursor/skills/<slug>/` | `~/.cursor/skills/<slug>/` | experimental |
52
+ | `copilot` (GitHub Copilot) | `.github/skills/<slug>/` | `~/.copilot/skills/<slug>/` | experimental |
53
+ | `kimi` | `.kimi/skills/<slug>/` | `~/.kimi/skills/<slug>/` | experimental |
54
+ | `openai` (OpenAI/GPT) | `.openai/skills/<slug>/` | `~/.openai/skills/<slug>/` | experimental |
55
+
56
+ Tools detected in your project (or home dir with `-g`) float to the top of the
57
+ picker and are preselected. `--dir <path>` installs anywhere. After installing,
58
+ **restart/reopen the tool** — most agents only scan skills at session start
59
+ (Claude Code: start a *new* session, then type `/<slug>`).
60
+
61
+ ## CI / non-interactive use
62
+
63
+ Non-TTY sessions never open a browser or show a picker. The contract:
64
+
65
+ ```bash
66
+ export PROMPTDOCK_TOKEN=pdk_… # Settings → Account → CLI sessions → Generate token
67
+ npx promptdock@latest install garry/seo-content-audit --target claude -y
68
+ ```
69
+
70
+ - `PROMPTDOCK_TOKEN` — bearer token (overrides any stored login).
71
+ - `PROMPTDOCK_API_BASE` — API origin override (default `https://promptdock.ai`).
72
+ - Without `--target`/`--dir` **and** `-y`, a non-interactive install exits `1`
73
+ with the exact flags to add. Without a token it exits `2`.
74
+ - `NO_COLOR` is honored.
75
+
76
+ ## Receipts, update, uninstall
77
+
78
+ Every install writes `<dir>/.promptdock.json` — the **receipt** (`schema: 1`):
79
+ skill id, ref, version, and the sha256 of every installed file. It is the
80
+ *only* authority the CLI trusts for later operations:
81
+
82
+ - `update` re-resolves each receipt and reinstalls when a newer approved
83
+ version exists (`--check` reports without applying). Bare `update` covers the
84
+ project; `-g` the global scope; `--all` both.
85
+ - `uninstall` removes **only** files the receipt lists, then the receipt, then
86
+ any emptied directories. Files you added are never touched.
87
+ - If you **edited** installed files, update/uninstall refuse without `--force`
88
+ ("copy your changes out first") — your local changes never die to a default.
89
+ - A receipt written by a newer CLI version is left alone with a
90
+ "run `npx promptdock@latest`" notice — never a guessy uninstall.
91
+
92
+ Downloads are verified against the server manifest (per-file sha256) and every
93
+ path is re-validated before any write; a failed check aborts **all-or-nothing**
94
+ ("integrity check failed — nothing installed").
95
+
96
+ ## Exit codes & errors
97
+
98
+ | Code | Meaning |
99
+ | --- | --- |
100
+ | 0 | success |
101
+ | 1 | usage (bad flags, missing `--target`/`-y` in CI) |
102
+ | 2 | auth (not logged in / token rejected) |
103
+ | 3 | denied (tier gate, daily cap, denial, rate limit, version floor, not found) |
104
+ | 4 | integrity (sha mismatch, unsafe manifest path, receipt schema) |
105
+ | 5 | filesystem (permissions, disk space, non-empty directory) |
106
+ | 6 | network |
107
+
108
+ Every named error links `https://promptdock.ai/docs/cli/errors#<code>`.
109
+
110
+ ## Notes
111
+
112
+ - Node.js ≥ 18. Zero runtime dependencies.
113
+ - The CLI always installs the **latest approved** version of a skill (no semver
114
+ pinning); every published skill has passed PromptDock's human review queue.
115
+ Review is a screening step, not a warranty — read what you install.
116
+ - Skills are plain files on your machine after install; premium gates access,
117
+ not copying.
package/dist/api.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import type { CliContext } from "./context.js";
2
+ export type ApiEnvelopeError = {
3
+ code: string;
4
+ message: string;
5
+ details?: Record<string, unknown>;
6
+ };
7
+ export type RawResponse = {
8
+ status: number;
9
+ body: unknown;
10
+ headers: Headers;
11
+ };
12
+ export declare class Api {
13
+ private readonly ctx;
14
+ readonly baseUrl: string;
15
+ private readonly token;
16
+ constructor(ctx: CliContext, baseUrl: string, token: string | null);
17
+ private headers;
18
+ /** Network-wrapped fetch; parses JSON (null body on parse failure). Never throws on HTTP status. */
19
+ raw(method: string, path: string, body?: unknown): Promise<RawResponse>;
20
+ /** Envelope-aware request: 2xx → typed body; anything else → a mapped CliError. */
21
+ request<T>(method: string, path: string, body?: unknown): Promise<T>;
22
+ /** Extract {error:{code,message,details}} defensively (untrusted input). */
23
+ static envelope(body: unknown): ApiEnvelopeError | null;
24
+ private mapEnvelopeError;
25
+ }
package/dist/api.js ADDED
@@ -0,0 +1,100 @@
1
+ import { CliError, EXIT, networkError } from "./errors.js";
2
+ export class Api {
3
+ ctx;
4
+ baseUrl;
5
+ token;
6
+ constructor(ctx, baseUrl, token) {
7
+ this.ctx = ctx;
8
+ this.baseUrl = baseUrl;
9
+ this.token = token;
10
+ }
11
+ headers(hasBody) {
12
+ const h = {
13
+ "user-agent": `promptdock-cli/${this.ctx.version}`,
14
+ "x-promptdock-cli-version": this.ctx.version,
15
+ "x-promptdock-cli-args": this.ctx.argsLine.slice(0, 120),
16
+ accept: "application/json",
17
+ };
18
+ if (hasBody)
19
+ h["content-type"] = "application/json";
20
+ if (this.token)
21
+ h.authorization = `Bearer ${this.token}`;
22
+ return h;
23
+ }
24
+ /** Network-wrapped fetch; parses JSON (null body on parse failure). Never throws on HTTP status. */
25
+ async raw(method, path, body) {
26
+ let res;
27
+ try {
28
+ res = await this.ctx.fetch(`${this.baseUrl}${path}`, {
29
+ method,
30
+ headers: this.headers(body !== undefined),
31
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
32
+ });
33
+ }
34
+ catch {
35
+ throw networkError(this.baseUrl, this.ctx.env);
36
+ }
37
+ let parsed = null;
38
+ try {
39
+ parsed = await res.json();
40
+ }
41
+ catch {
42
+ /* non-JSON body (should not happen on /api/v1) */
43
+ }
44
+ return { status: res.status, body: parsed, headers: res.headers };
45
+ }
46
+ /** Envelope-aware request: 2xx → typed body; anything else → a mapped CliError. */
47
+ async request(method, path, body) {
48
+ const res = await this.raw(method, path, body);
49
+ if (res.status >= 200 && res.status < 300)
50
+ return res.body;
51
+ throw this.mapEnvelopeError(res);
52
+ }
53
+ /** Extract {error:{code,message,details}} defensively (untrusted input). */
54
+ static envelope(body) {
55
+ if (typeof body !== "object" || body === null)
56
+ return null;
57
+ const err = body.error;
58
+ if (typeof err !== "object" || err === null)
59
+ return null;
60
+ const e = err;
61
+ if (typeof e.code !== "string")
62
+ return null;
63
+ return {
64
+ code: e.code,
65
+ message: typeof e.message === "string" ? e.message : e.code,
66
+ details: typeof e.details === "object" && e.details !== null
67
+ ? e.details
68
+ : undefined,
69
+ };
70
+ }
71
+ mapEnvelopeError(res) {
72
+ const env = Api.envelope(res.body);
73
+ // 426 version floor: print the SERVER's remedy verbatim (DX3/F8) — the
74
+ // server knows the canonical command better than a stale client does.
75
+ if (res.status === 426) {
76
+ const remedy = typeof env?.details?.remedy === "string" ? env.details.remedy : null;
77
+ const msg = env?.message ?? "This promptdock CLI version is no longer supported.";
78
+ return new CliError(remedy ? `${msg}\n${remedy}` : msg, EXIT.DENIED, {
79
+ footer: "upgrade_required",
80
+ });
81
+ }
82
+ if (res.status === 401) {
83
+ return new CliError(env?.message ?? "Not authenticated.", EXIT.AUTH, {
84
+ footer: "auth",
85
+ hint: "run: npx promptdock@latest login",
86
+ });
87
+ }
88
+ if (res.status === 429) {
89
+ const retryAfter = res.headers.get("retry-after");
90
+ const secs = retryAfter ? Number(retryAfter) : null;
91
+ const wait = secs && Number.isFinite(secs) ? ` Retry in ~${Math.ceil(secs / 60)} min.` : "";
92
+ return new CliError((env?.message ?? "Rate limited.") + wait, EXIT.DENIED, {
93
+ footer: env?.code ?? "rate_limited",
94
+ });
95
+ }
96
+ return new CliError(env?.message ?? `request failed (HTTP ${res.status})`, EXIT.DENIED, {
97
+ footer: env?.code ?? "api",
98
+ });
99
+ }
100
+ }
package/dist/args.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ export type ParsedArgs = {
2
+ command: string | null;
3
+ positionals: string[];
4
+ flags: Record<string, string | boolean>;
5
+ };
6
+ export type FlagSpec = {
7
+ /** flags that take NO value (presence = true) */
8
+ booleans: string[];
9
+ /** flags that REQUIRE a value (`--flag value` or `--flag=value`) */
10
+ values: string[];
11
+ /** short → long aliases (e.g. g → global, y → yes) */
12
+ aliases?: Record<string, string>;
13
+ };
14
+ export declare const GLOBAL_ALIASES: Record<string, string>;
15
+ export declare const COMMAND_SPECS: Record<string, FlagSpec>;
16
+ /** `upgrade` aliases `update` (DX2 npm muscle memory). */
17
+ export declare const COMMAND_ALIASES: Record<string, string>;
18
+ export declare function parseArgs(argv: string[]): ParsedArgs;
package/dist/args.js ADDED
@@ -0,0 +1,103 @@
1
+ // Hand-rolled arg parser (zero-dep bias). Spec-driven: each command declares its
2
+ // boolean flags and value-taking flags; unknown flags are a usage error (exit 1).
3
+ import { usageError } from "./errors.js";
4
+ export const GLOBAL_ALIASES = {
5
+ g: "global",
6
+ y: "yes",
7
+ h: "help",
8
+ v: "version",
9
+ };
10
+ export const COMMAND_SPECS = {
11
+ login: { booleans: ["help"], values: ["token"] },
12
+ logout: { booleans: ["help"], values: [] },
13
+ whoami: { booleans: ["help", "json"], values: [] },
14
+ install: {
15
+ booleans: ["help", "json", "global", "yes", "force", "dry-run"],
16
+ values: ["target", "dir"],
17
+ },
18
+ uninstall: {
19
+ booleans: ["help", "json", "global", "yes", "force", "all"],
20
+ values: ["target", "dir"],
21
+ },
22
+ update: {
23
+ booleans: ["help", "json", "global", "yes", "force", "all", "check"],
24
+ values: ["target", "dir"],
25
+ },
26
+ list: { booleans: ["help", "json", "global", "all"], values: [] },
27
+ help: { booleans: [], values: [] },
28
+ };
29
+ /** `upgrade` aliases `update` (DX2 npm muscle memory). */
30
+ export const COMMAND_ALIASES = { upgrade: "update" };
31
+ export function parseArgs(argv) {
32
+ const flags = {};
33
+ const positionals = [];
34
+ let command = null;
35
+ // Global, command-less forms first.
36
+ const first = argv[0];
37
+ if (first === "--version" || first === "-v")
38
+ return { command: "version", positionals: [], flags: {} };
39
+ if (first === "--help" || first === "-h" || first === undefined) {
40
+ return { command: "help", positionals: [], flags: {} };
41
+ }
42
+ if (first.startsWith("-"))
43
+ throw usageError(`unknown option "${first}"`, "run: promptdock --help");
44
+ command = COMMAND_ALIASES[first] ?? first;
45
+ const spec = COMMAND_SPECS[command];
46
+ if (!spec) {
47
+ throw usageError(`unknown command "${first}"`, "run: promptdock --help");
48
+ }
49
+ const canon = (name) => spec.aliases?.[name] ?? GLOBAL_ALIASES[name] ?? name;
50
+ const rest = argv.slice(1);
51
+ for (let i = 0; i < rest.length; i++) {
52
+ const a = rest[i];
53
+ if (a === "--") {
54
+ positionals.push(...rest.slice(i + 1));
55
+ break;
56
+ }
57
+ if (a.startsWith("--")) {
58
+ const eq = a.indexOf("=");
59
+ const name = canon(eq === -1 ? a.slice(2) : a.slice(2, eq));
60
+ if (spec.booleans.includes(name)) {
61
+ if (eq !== -1)
62
+ throw usageError(`--${name} takes no value`);
63
+ flags[name] = true;
64
+ }
65
+ else if (spec.values.includes(name)) {
66
+ const value = eq !== -1 ? a.slice(eq + 1) : rest[++i];
67
+ if (value === undefined || (eq === -1 && value.startsWith("-"))) {
68
+ throw usageError(`--${name} requires a value`);
69
+ }
70
+ flags[name] = value;
71
+ }
72
+ else {
73
+ throw usageError(`unknown option "--${name}" for ${command}`, `run: promptdock ${command} --help`);
74
+ }
75
+ }
76
+ else if (a.startsWith("-") && a.length > 1) {
77
+ // Short flags; bundles allowed for booleans (-gy). A value-taking short
78
+ // flag must be last in the bundle.
79
+ const chars = a.slice(1).split("");
80
+ for (let c = 0; c < chars.length; c++) {
81
+ const name = canon(chars[c]);
82
+ if (spec.booleans.includes(name)) {
83
+ flags[name] = true;
84
+ }
85
+ else if (spec.values.includes(name)) {
86
+ if (c !== chars.length - 1)
87
+ throw usageError(`-${chars[c]} requires a value`);
88
+ const value = rest[++i];
89
+ if (value === undefined || value.startsWith("-"))
90
+ throw usageError(`-${chars[c]} requires a value`);
91
+ flags[name] = value;
92
+ }
93
+ else {
94
+ throw usageError(`unknown option "-${chars[c]}" for ${command}`, `run: promptdock ${command} --help`);
95
+ }
96
+ }
97
+ }
98
+ else {
99
+ positionals.push(a);
100
+ }
101
+ }
102
+ return { command, positionals, flags };
103
+ }
package/dist/auth.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { Api } from "./api.js";
2
+ import type { CliContext } from "./context.js";
3
+ /**
4
+ * Run the browser hand-off and return the bearer token. NEVER logs the token.
5
+ * DX1: a code that expires while waiting AUTO-RESTARTS the flow ONCE (new code,
6
+ * reprint, keep waiting); a second expiry is the honest timeout.
7
+ */
8
+ export declare function deviceFlowLogin(ctx: CliContext, baseUrl: string, opts: {
9
+ long: boolean;
10
+ }): Promise<string>;
11
+ /**
12
+ * Resolve auth for a token-authed command. Env PROMPTDOCK_TOKEN first (CI),
13
+ * then the stored config; no token + TTY → inline device-flow login with the
14
+ * DX1 LONG (15-min) code (this is the "start follows a login_required verdict"
15
+ * path — a fresh signup races email confirmation); no token + non-TTY → exit 2.
16
+ */
17
+ export declare function ensureAuth(ctx: CliContext): Promise<{
18
+ api: Api;
19
+ baseUrl: string;
20
+ }>;
package/dist/auth.js ADDED
@@ -0,0 +1,135 @@
1
+ // Device-flow login (RFC-8628 client half) + the shared "make sure we hold a
2
+ // token" helper. The poll family speaks RFC verbs at the TOP level of the body
3
+ // (the one envelope exception) — so it rides Api.raw, never Api.request.
4
+ import { hostname } from "node:os";
5
+ import { Api } from "./api.js";
6
+ import { loadConfig, resolveApiBase, resolveToken, saveConfig } from "./config.js";
7
+ import { CliError, EXIT } from "./errors.js";
8
+ import { formatCountdown } from "./ui.js";
9
+ /**
10
+ * Run the browser hand-off and return the bearer token. NEVER logs the token.
11
+ * DX1: a code that expires while waiting AUTO-RESTARTS the flow ONCE (new code,
12
+ * reprint, keep waiting); a second expiry is the honest timeout.
13
+ */
14
+ export async function deviceFlowLogin(ctx, baseUrl, opts) {
15
+ const api = new Api(ctx, baseUrl, null);
16
+ let restarts = 0;
17
+ for (;;) {
18
+ const start = await api.request("POST", "/api/v1/cli/auth/start", {
19
+ device_label: safeDeviceLabel(),
20
+ long: opts.long,
21
+ });
22
+ ctx.io.out("");
23
+ ctx.io.out("Confirm this code in your browser:");
24
+ ctx.io.out("");
25
+ ctx.io.out(` ${start.user_code}`);
26
+ ctx.io.out("");
27
+ ctx.io.out(`Open: ${start.verification_url}`);
28
+ if (ctx.io.isTTY) {
29
+ ctx.openUrl(start.verification_url);
30
+ ctx.io.out("(if the page didn't open, paste the URL into your browser)");
31
+ }
32
+ ctx.io.out("");
33
+ const deadline = ctx.now() + Math.max(1, start.expires_in) * 1000;
34
+ let intervalSecs = Math.max(0, Number(start.interval) || 0);
35
+ poll: for (;;) {
36
+ if (ctx.now() >= deadline)
37
+ break poll; // local expiry — restart below
38
+ await waitWithCountdown(ctx, intervalSecs, deadline);
39
+ const res = await api.raw("POST", "/api/v1/cli/auth/poll", {
40
+ device_code: start.device_code,
41
+ });
42
+ if (res.status >= 200 && res.status < 300) {
43
+ const body = res.body;
44
+ if (body && typeof body.access_token === "string") {
45
+ clearCountdown(ctx);
46
+ return body.access_token;
47
+ }
48
+ break poll; // malformed success — treat as expired, restart once
49
+ }
50
+ const verb = pollVerb(res.body);
51
+ if (verb === "authorization_pending")
52
+ continue;
53
+ if (verb === "slow_down") {
54
+ const retryAfter = Number(res.headers.get("retry-after"));
55
+ intervalSecs = Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter : intervalSecs + 5;
56
+ continue;
57
+ }
58
+ clearCountdown(ctx);
59
+ if (verb === "access_denied") {
60
+ throw new CliError("Login was denied from the browser.", EXIT.DENIED, {
61
+ footer: "access_denied",
62
+ });
63
+ }
64
+ break poll; // expired_token (or unknown) — restart below
65
+ }
66
+ clearCountdown(ctx);
67
+ restarts += 1;
68
+ if (restarts > 1) {
69
+ throw new CliError("login timed out — run `npx promptdock@latest login` to retry", EXIT.AUTH, {
70
+ footer: "login_timeout",
71
+ });
72
+ }
73
+ ctx.io.out("That code expired — starting a fresh one…");
74
+ opts = { long: true };
75
+ }
76
+ }
77
+ /**
78
+ * Resolve auth for a token-authed command. Env PROMPTDOCK_TOKEN first (CI),
79
+ * then the stored config; no token + TTY → inline device-flow login with the
80
+ * DX1 LONG (15-min) code (this is the "start follows a login_required verdict"
81
+ * path — a fresh signup races email confirmation); no token + non-TTY → exit 2.
82
+ */
83
+ export async function ensureAuth(ctx) {
84
+ const config = loadConfig(ctx.home);
85
+ const baseUrl = resolveApiBase(ctx.env, config);
86
+ let token = resolveToken(ctx.env, config);
87
+ if (!token) {
88
+ if (!ctx.io.isTTY) {
89
+ throw new CliError("not logged in — set the PROMPTDOCK_TOKEN environment variable, or run `npx promptdock@latest login` interactively", EXIT.AUTH, { footer: "auth" });
90
+ }
91
+ ctx.io.out("You need to log in first.");
92
+ token = await deviceFlowLogin(ctx, baseUrl, { long: true });
93
+ saveConfig(ctx.home, { ...config, token, api_base: baseUrl }, ctx.platform);
94
+ ctx.io.out("✓ Logged in.");
95
+ }
96
+ return { api: new Api(ctx, baseUrl, token), baseUrl };
97
+ }
98
+ function pollVerb(body) {
99
+ if (typeof body !== "object" || body === null)
100
+ return null;
101
+ const v = body.error;
102
+ return v === "authorization_pending" || v === "slow_down" || v === "access_denied" || v === "expired_token"
103
+ ? v
104
+ : null;
105
+ }
106
+ /** Live countdown (TTY repaint; non-TTY just sleeps — no log spam in CI). */
107
+ async function waitWithCountdown(ctx, secs, deadlineMs) {
108
+ const end = Math.min(ctx.now() + secs * 1000, deadlineMs);
109
+ if (!ctx.io.isTTY) {
110
+ const ms = end - ctx.now();
111
+ if (ms > 0)
112
+ await ctx.sleep(ms);
113
+ return;
114
+ }
115
+ for (;;) {
116
+ const remainMs = end - ctx.now();
117
+ const left = Math.max(0, Math.round((deadlineMs - ctx.now()) / 1000));
118
+ ctx.io.write(`\rWaiting for approval… ${formatCountdown(left)} `);
119
+ if (remainMs <= 0)
120
+ return;
121
+ await ctx.sleep(Math.min(1000, remainMs));
122
+ }
123
+ }
124
+ function clearCountdown(ctx) {
125
+ if (ctx.io.isTTY)
126
+ ctx.io.write("\r\u001b[2K");
127
+ }
128
+ function safeDeviceLabel() {
129
+ try {
130
+ return hostname().slice(0, 64) || "promptdock-cli";
131
+ }
132
+ catch {
133
+ return "promptdock-cli";
134
+ }
135
+ }
@@ -0,0 +1,2 @@
1
+ import type { CliContext } from "../context.js";
2
+ export declare function runInstall(ctx: CliContext, positionals: string[], flags: Record<string, string | boolean>): Promise<void>;