@shipd-ai/cli 0.1.1 → 0.1.3

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
@@ -16,15 +16,16 @@ Admin docs, the supported checks, and a copy-paste setup prompt for coding agent
16
16
 
17
17
  ```bash
18
18
  npm install -g @shipd-ai/cli
19
- shipd login # opens the quest's login page (olympus by default); or: shipd login --token <jwt>
19
+ shipd login # opens https://shipd.ai/cli-auth (one token for every quest); or: shipd login --token <jwt>
20
20
  shipd whoami
21
21
  shipd quests # which quests this CLI can talk to
22
22
  ```
23
23
 
24
24
  `login` opens the token page in your browser; paste the token back into the
25
- terminal. It is stored at `~/.shipd/credentials.json` (mode 0600). The token is
26
- personal: never commit it, never pass it on a command line. `shipd logout`
27
- deletes it.
25
+ terminal. `--no-browser` prints the page URL instead of opening it. The token is
26
+ stored at `~/.shipd/credentials.json` (mode 0600). It is personal: never commit
27
+ or share it. Typing `--token` by hand leaves it in your shell history, so prefer
28
+ the prompt there. `shipd logout` deletes it.
28
29
 
29
30
  ## Three input modes
30
31
 
@@ -110,6 +111,7 @@ check with `status`, `verdict`, `output`, `error`, `durationMs`, and `steps`
110
111
 
111
112
  | Variable | Effect |
112
113
  | --- | --- |
114
+ | `SHIPD_AUTH_URL` | Override the platform login page `shipd login` opens (default `https://shipd.ai/cli-auth`). |
113
115
  | `SHIPD_<QUEST>_URL` | Override the quest's base URL (for example `SHIPD_OLYMPUS_URL`). |
114
116
  | `SHIPD_<QUEST>_CONVEX_URL` | Skip `/api/cli/config` and talk to this Convex deployment. |
115
117
  | `SHIPD_HOME` | Credentials and caches directory (default `~/.shipd`). |
@@ -3,14 +3,15 @@ export declare const login: import("citty").CommandDef<{
3
3
  readonly type: "string";
4
4
  readonly description: "Paste the token directly instead of prompting";
5
5
  };
6
- readonly "no-browser": {
6
+ readonly browser: {
7
7
  readonly type: "boolean";
8
- readonly description: "Print the login URL instead of opening it";
8
+ readonly default: true;
9
+ readonly description: "Open the login page in a browser";
10
+ readonly negativeDescription: "Print the login URL instead of opening a browser";
9
11
  };
10
12
  readonly quest: {
11
13
  readonly type: "string";
12
- readonly description: "Quest whose login page to open (default: olympus)";
13
- readonly default: "olympus";
14
+ readonly description: "Open this quest's own login page instead of the platform page (the token works for every quest either way)";
14
15
  };
15
16
  }>;
16
17
  export declare const logout: import("citty").CommandDef<import("citty").ArgsDef>;
@@ -4,7 +4,7 @@ import { defineCommand } from "citty";
4
4
  import { clearCredentials, loadCredentials, saveCredentials } from "../auth.js";
5
5
  import { UsageError } from "../errors.js";
6
6
  import { printJson } from "../format.js";
7
- import { DEFAULT_QUEST, questAuthUrl } from "../quests.js";
7
+ import { platformAuthUrl, questAuthUrl } from "../quests.js";
8
8
  function openBrowser(url) {
9
9
  if (process.platform === "win32") {
10
10
  // `start` is a cmd.exe built-in, not a standalone executable.
@@ -18,7 +18,18 @@ function openBrowser(url) {
18
18
  function prompt(question) {
19
19
  const rl = createInterface({ input: process.stdin, output: process.stdout });
20
20
  return new Promise((resolve) => {
21
+ let answered = false;
22
+ // stdin can end before an answer (a non-interactive shell with nothing
23
+ // piped in). Resolve empty so login fails with a usage error; otherwise
24
+ // the process drains and exits 0 with nothing saved.
25
+ rl.on("close", () => {
26
+ if (answered)
27
+ return;
28
+ process.stdout.write("\n");
29
+ resolve("");
30
+ });
21
31
  rl.question(question, (answer) => {
32
+ answered = true;
22
33
  rl.close();
23
34
  resolve(answer.trim());
24
35
  });
@@ -27,8 +38,7 @@ function prompt(question) {
27
38
  const questArg = {
28
39
  quest: {
29
40
  type: "string",
30
- description: `Quest whose login page to open (default: ${DEFAULT_QUEST})`,
31
- default: DEFAULT_QUEST,
41
+ description: "Open this quest's own login page instead of the platform page (the token works for every quest either way)",
32
42
  },
33
43
  };
34
44
  export const login = defineCommand({
@@ -36,23 +46,31 @@ export const login = defineCommand({
36
46
  args: {
37
47
  ...questArg,
38
48
  token: { type: "string", description: "Paste the token directly instead of prompting" },
39
- "no-browser": { type: "boolean", description: "Print the login URL instead of opening it" },
49
+ // Named `browser` with a true default because citty parses `--no-browser`
50
+ // as browser=false; an arg literally named "no-browser" is never set.
51
+ browser: {
52
+ type: "boolean",
53
+ default: true,
54
+ description: "Open the login page in a browser",
55
+ negativeDescription: "Print the login URL instead of opening a browser",
56
+ },
40
57
  },
41
58
  run: async ({ args }) => {
42
- const authUrl = questAuthUrl(args.quest);
59
+ const authUrl = args.quest ? questAuthUrl(args.quest) : platformAuthUrl();
43
60
  let token = args.token?.trim();
44
61
  if (!token) {
45
- if (args["no-browser"]) {
46
- console.log(`\n Open this page and copy the token:\n ${authUrl}\n`);
47
- }
48
- else {
62
+ if (args.browser) {
49
63
  console.log(`\n Opening browser...\n ${authUrl}\n`);
50
64
  openBrowser(authUrl);
51
65
  }
66
+ else {
67
+ console.log(`\n Open this page and copy the token:\n ${authUrl}\n`);
68
+ }
52
69
  token = await prompt(" Paste your token: ");
53
70
  }
54
- if (!token)
55
- throw new UsageError("No token provided.");
71
+ if (!token) {
72
+ throw new UsageError("No token provided. Pass it with --token, or paste it at the prompt in an interactive terminal.");
73
+ }
56
74
  const identity = saveCredentials(token);
57
75
  const expiresAt = new Date(identity.exp * 1000).toLocaleDateString();
58
76
  console.log(`\n Logged in as ${identity.name || identity.email || identity.sub}`);
package/dist/quests.d.ts CHANGED
@@ -4,7 +4,9 @@ export declare const DEFAULT_QUEST = "olympus";
4
4
  export declare function knownQuests(): string[];
5
5
  /** `SHIPD_<QUEST>_URL` override, or NODE_ENV=development → localhost. */
6
6
  export declare function questBaseUrl(quest: string): string;
7
- /** The page that mints the CLI token for the browser login flow. */
7
+ /** `SHIPD_AUTH_URL` override, or NODE_ENV=development → localhost. */
8
+ export declare function platformAuthUrl(): string;
9
+ /** A quest's own copy of the token page (the platform page is the default). */
8
10
  export declare function questAuthUrl(quest: string): string;
9
11
  export interface QuestConfig {
10
12
  convexUrl: string;
package/dist/quests.js CHANGED
@@ -32,7 +32,21 @@ export function questBaseUrl(quest) {
32
32
  }
33
33
  return process.env.NODE_ENV === "development" ? entry.dev : entry.prod;
34
34
  }
35
- /** The page that mints the CLI token for the browser login flow. */
35
+ // The platform-level token page. The shipd-cli token works for every quest,
36
+ // so the browser login lands here; `questAuthUrl` stays for a quest's own
37
+ // copy of the page (`shipd login --quest <quest>`).
38
+ const PLATFORM_AUTH_URL = {
39
+ prod: "https://shipd.ai/cli-auth",
40
+ dev: "http://localhost:3010/cli-auth",
41
+ };
42
+ /** `SHIPD_AUTH_URL` override, or NODE_ENV=development → localhost. */
43
+ export function platformAuthUrl() {
44
+ const override = process.env.SHIPD_AUTH_URL;
45
+ if (override)
46
+ return override;
47
+ return process.env.NODE_ENV === "development" ? PLATFORM_AUTH_URL.dev : PLATFORM_AUTH_URL.prod;
48
+ }
49
+ /** A quest's own copy of the token page (the platform page is the default). */
36
50
  export function questAuthUrl(quest) {
37
51
  return `${questBaseUrl(quest)}/cli-auth`;
38
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipd-ai/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Shipd admin CLI — run quest checks on Harbor tasks or existing tasks",
5
5
  "type": "module",
6
6
  "homepage": "https://shipd.ai",