promptdock 1.0.0 → 1.0.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/README.md CHANGED
@@ -68,7 +68,10 @@ npx promptdock@latest install garry/seo-content-audit --target claude -y
68
68
  ```
69
69
 
70
70
  - `PROMPTDOCK_TOKEN` — bearer token (overrides any stored login).
71
- - `PROMPTDOCK_API_BASE` — API origin override (default `https://promptdock.ai`).
71
+ - `PROMPTDOCK_API_BASE` — API origin override (default `https://www.promptdock.ai`).
72
+ Must be the origin the API is **served** on. Pointing it at a host that redirects
73
+ will not work: a cross-origin redirect drops the request body and strips the
74
+ `Authorization` header, so the CLI refuses to follow one and tells you the target.
72
75
  - Without `--target`/`--dir` **and** `-y`, a non-interactive install exits `1`
73
76
  with the exact flags to add. Without a token it exits `2`.
74
77
  - `NO_COLOR` is honored.
package/dist/api.js CHANGED
@@ -1,4 +1,13 @@
1
1
  import { CliError, EXIT, networkError } from "./errors.js";
2
+ /** Origin of a Location header, for the "set PROMPTDOCK_API_BASE to …" hint. */
3
+ function originOf(location) {
4
+ try {
5
+ return new URL(location).origin;
6
+ }
7
+ catch {
8
+ return null; // relative Location — can't name an origin, fall back to the default
9
+ }
10
+ }
2
11
  export class Api {
3
12
  ctx;
4
13
  baseUrl;
@@ -28,12 +37,27 @@ export class Api {
28
37
  res = await this.ctx.fetch(`${this.baseUrl}${path}`, {
29
38
  method,
30
39
  headers: this.headers(body !== undefined),
40
+ // NEVER "follow" here. A cross-origin redirect silently downgrades POST to
41
+ // GET, drops the body, AND strips Authorization — so following one turns a
42
+ // misconfigured origin into a 405 or a bogus 401 with no hint of the cause.
43
+ // NEVER "error" either: that rejects with a TypeError, which the catch below
44
+ // would report as a connectivity failure — the opposite of the truth.
45
+ redirect: "manual",
31
46
  ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
32
47
  });
33
48
  }
34
49
  catch {
35
50
  throw networkError(this.baseUrl, this.ctx.env);
36
51
  }
52
+ // Only the API client refuses redirects. Signed-URL downloads (installer.ts) use
53
+ // ctx.fetch directly and must keep following them — storage hands out redirects.
54
+ if (res.status >= 300 && res.status < 400) {
55
+ const to = res.headers.get("location") ?? "(no location header)";
56
+ throw new CliError(`${this.baseUrl} redirected to ${to} — the API must be called on its canonical origin, because a redirect drops the request body and the login token.`, EXIT.NETWORK, {
57
+ hint: `set PROMPTDOCK_API_BASE to the redirect target, e.g. PROMPTDOCK_API_BASE=${originOf(to) ?? "https://www.promptdock.ai"}`,
58
+ footer: "api",
59
+ });
60
+ }
37
61
  let parsed = null;
38
62
  try {
39
63
  parsed = await res.json();
package/dist/config.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const DEFAULT_API_BASE = "https://promptdock.ai";
1
+ export declare const DEFAULT_API_BASE = "https://www.promptdock.ai";
2
2
  export type CliConfig = {
3
3
  token?: string;
4
4
  api_base?: string;
package/dist/config.js CHANGED
@@ -5,7 +5,23 @@
5
5
  import { chmodSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
6
6
  import { join } from "node:path";
7
7
  import { CliError, EXIT } from "./errors.js";
8
- export const DEFAULT_API_BASE = "https://promptdock.ai";
8
+ // ⚠️ MUST be the host the API is actually SERVED on, not the brand apex.
9
+ // `promptdock.ai` 301-redirects to `www.promptdock.ai`, and Node's fetch follows
10
+ // that redirect ACROSS AN ORIGIN — which per the fetch spec (a) downgrades POST to
11
+ // GET and drops the body, and (b) STRIPS the `Authorization` header. Both, at any
12
+ // status code: a 308 preserves the method and still scrubs the bearer. So pointing
13
+ // this at the apex breaks every call — login POSTs answered with 405, and every
14
+ // authenticated GET arriving anonymous and answered 401. Keep this in lockstep with
15
+ // SITE_URL in lib/site-url.ts; `base-url.test.ts` fails the build if they diverge.
16
+ export const DEFAULT_API_BASE = "https://www.promptdock.ai";
17
+ /**
18
+ * Origins a PREVIOUS release persisted into ~/.promptdock/config.json and which are
19
+ * known-broken. A successful login writes `api_base`, and a stored value outranks
20
+ * DEFAULT_API_BASE — so without this, everyone who ever logged in with <=1.0.0 would
21
+ * keep hitting the apex forever, even after upgrading. Self-healing beats asking
22
+ * every user to log out.
23
+ */
24
+ const LEGACY_API_BASES = new Set(["https://promptdock.ai", "http://promptdock.ai"]);
9
25
  export function configDir(home) {
10
26
  return join(home, ".promptdock");
11
27
  }
@@ -56,9 +72,19 @@ export function saveConfig(home, config, platform) {
56
72
  export function resolveToken(env, config) {
57
73
  return env.PROMPTDOCK_TOKEN || (typeof config.token === "string" ? config.token : null) || null;
58
74
  }
75
+ const trimBase = (s) => s.replace(/\/+$/, "");
76
+ // ⚠️ Login persists `api_base` ALONGSIDE the token, deliberately — do not "fix" this
77
+ // to skip persistence when the origin came from PROMPTDOCK_API_BASE. A token is
78
+ // minted by one specific database (prod and staging are separate Supabase projects),
79
+ // so the token and the origin are a PAIR. Storing the token without its origin means
80
+ // the next run sends a staging token to the default origin and 401s. The apex-pinning
81
+ // problem that motivated the idea is handled by LEGACY_API_BASES below instead.
59
82
  export function resolveApiBase(env, config) {
60
- const base = env.PROMPTDOCK_API_BASE ||
61
- (typeof config.api_base === "string" ? config.api_base : null) ||
62
- DEFAULT_API_BASE;
63
- return base.replace(/\/+$/, "");
83
+ if (env.PROMPTDOCK_API_BASE)
84
+ return trimBase(env.PROMPTDOCK_API_BASE);
85
+ const stored = typeof config.api_base === "string" ? trimBase(config.api_base) : null;
86
+ // Drop a base a broken release pinned here; anything else the user set is honoured.
87
+ if (stored && !LEGACY_API_BASES.has(stored))
88
+ return stored;
89
+ return trimBase(DEFAULT_API_BASE);
64
90
  }
package/dist/context.d.ts CHANGED
@@ -1,3 +1,14 @@
1
+ /**
2
+ * argv joined for the `X-Promptdock-Cli-Args` header, with bearer tokens removed.
3
+ *
4
+ * That header rides EVERY request so the server can echo a copy-pasteable remedy in
5
+ * a 426. But the documented CI path is `promptdock login --token pdk_…`, so joining
6
+ * argv raw put the user's live secret in a custom header on every call — logged by
7
+ * every proxy in between, and (before the redirect fix) sent to whatever host the
8
+ * apex happened to redirect to. Redact the value, not the flag: the server's remedy
9
+ * still needs to show that `--token` was passed.
10
+ */
11
+ export declare function redactArgs(argv: string[]): string;
1
12
  /** I/O seam — commands never touch process.* directly (unit tests inject fakes). */
2
13
  export type CliIo = {
3
14
  /** stdout line */
package/dist/context.js CHANGED
@@ -2,6 +2,30 @@ import { spawn } from "node:child_process";
2
2
  import { homedir } from "node:os";
3
3
  import { createInterface } from "node:readline/promises";
4
4
  import { readFileSync } from "node:fs";
5
+ /**
6
+ * argv joined for the `X-Promptdock-Cli-Args` header, with bearer tokens removed.
7
+ *
8
+ * That header rides EVERY request so the server can echo a copy-pasteable remedy in
9
+ * a 426. But the documented CI path is `promptdock login --token pdk_…`, so joining
10
+ * argv raw put the user's live secret in a custom header on every call — logged by
11
+ * every proxy in between, and (before the redirect fix) sent to whatever host the
12
+ * apex happened to redirect to. Redact the value, not the flag: the server's remedy
13
+ * still needs to show that `--token` was passed.
14
+ */
15
+ export function redactArgs(argv) {
16
+ return argv
17
+ .map((a, i) => {
18
+ if (/^pdk_/.test(a))
19
+ return "pdk_***";
20
+ // `--token <value>` / `--token=<value>`
21
+ if (/^--token=/.test(a))
22
+ return "--token=***";
23
+ if (argv[i - 1] === "--token")
24
+ return "***";
25
+ return a;
26
+ })
27
+ .join(" ");
28
+ }
5
29
  /** Read the package's own version (dist/ and src/ both sit one level under the root). */
6
30
  export function readOwnVersion() {
7
31
  try {
@@ -53,7 +77,7 @@ export function realContext(argv) {
53
77
  platform: process.platform,
54
78
  fetch: globalThis.fetch,
55
79
  version: readOwnVersion(),
56
- argsLine: argv.join(" "),
80
+ argsLine: redactArgs(argv),
57
81
  now: () => Date.now(),
58
82
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
59
83
  openUrl: (url) => openUrlBestEffort(url, process.platform),
package/dist/help.js CHANGED
@@ -39,7 +39,7 @@ ${targets}
39
39
 
40
40
  Environment
41
41
  PROMPTDOCK_TOKEN bearer token for CI/non-interactive use (Settings → CLI sessions → Generate token)
42
- PROMPTDOCK_API_BASE API origin override (default https://promptdock.ai)
42
+ PROMPTDOCK_API_BASE API origin override (default https://www.promptdock.ai)
43
43
  NO_COLOR disable colored output
44
44
 
45
45
  Exit codes
package/package.json CHANGED
@@ -1,9 +1,14 @@
1
1
  {
2
2
  "name": "promptdock",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Install AI agent skills from PromptDock — npx promptdock@latest install <handle>/<slug>",
5
5
  "keywords": ["promptdock", "skills", "ai", "agents", "claude", "cli"],
6
6
  "homepage": "https://promptdock.ai",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/klicklabs/promptdock.ai.git",
10
+ "directory": "packages/cli"
11
+ },
7
12
  "bugs": { "url": "https://promptdock.ai/docs/cli/errors" },
8
13
  "license": "UNLICENSED",
9
14
  "type": "module",