@elmundi/ship-cli 0.7.0 → 0.8.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
@@ -1,60 +1,91 @@
1
1
  # @elmundi/ship-cli
2
2
 
3
- Command-line entry to the Ship methodology: **one HTTP API** (FastAPI) for **search, fetch, feedback, patterns, tools, workflows, collections** or read catalogs from disk inside a Ship clone / `SHIP_REPO` — plus **`ship init`** to inject API usage into agent configs.
3
+ **Ship** in your repository: agents get a standing policy to call the **methodology HTTP API** (semantic search, full doc fetch, catalog bodies, retro feedback) via the **`ship`** CLI and the URLs/snippets `ship init` writes.
4
4
 
5
- Published under the npm org **[elmundi](https://www.npmjs.com/org/elmundi)**; the binary name remains **`ship`**.
5
+ Published as **`@elmundi/ship-cli`** under the [elmundi](https://www.npmjs.com/org/elmundi) org; the binary name is **`ship`**.
6
6
 
7
7
  ## Requirements
8
8
 
9
- - **Node.js 20+** (matches Ship CI and typical adopters).
9
+ - **Node.js 20+**
10
10
 
11
11
  ## Install
12
12
 
13
- After the package is [published to npm](https://www.npmjs.com/package/@elmundi/ship-cli):
14
-
15
13
  ```bash
16
14
  npm install -g @elmundi/ship-cli
17
- # or, without a global install:
15
+ # or one-off:
18
16
  npx @elmundi/ship-cli help
19
17
  ```
20
18
 
21
- From a full **Ship** monorepo clone you can still run `npm run ship -- …` from the repo root (workspace).
19
+ ## Bring Ship into your project (main path)
22
20
 
23
- ## Adopt without cloning the whole monorepo
21
+ You **do not** need the Ship monorepo cloned for day-to-day use. Work in **your product repo** and wire agents to the same methodology API the CLI uses.
24
22
 
25
- 1. Install the CLI (`npm i -g @elmundi/ship-cli` or use `npx @elmundi/ship-cli`).
26
- 2. From **any** directory, point **`SHIP_API_BASE`** at the **deployed methodology API** and list patterns or catalogs (same server as search):
23
+ ### 1. Pick the API URL
27
24
 
28
- ```bash
29
- SHIP_API_BASE=https://your-ship-api.example.com npx @elmundi/ship-cli patterns list
30
- SHIP_API_BASE=https://your-ship-api.example.com npx @elmundi/ship-cli tools list
31
- ```
25
+ - **`SHIP_API_BASE`** — env var the CLI and injected snippets use (no trailing slash).
26
+ - Or pass **`--base-url`** on each command.
27
+ - Default matches other Ship tooling (public methodology host unless you override for local FastAPI).
32
28
 
33
- 3. Optional: work from a **local** Ship checkout (or **`SHIP_REPO`**) to read manifests from disk without calling the API.
29
+ ### 2. Preview what `ship init` will change
34
30
 
35
- 4. In your **product** repository, wire agents to the methodology API:
31
+ From the **root of the repo** you want agents to use:
36
32
 
37
- ```bash
38
- cd /path/to/your-product
39
- npx @elmundi/ship-cli init --yes
40
- ```
33
+ ```bash
34
+ cd /path/to/your-product
35
+ npx @elmundi/ship-cli init --dry-run
36
+ ```
41
37
 
42
- Use **`--dry-run`** first to preview; **`--yes`** skips prompts and writes files see `ship init help`.
38
+ `ship init` **detects what is already in the tree** and only plans injections for those stacks:
43
39
 
44
- ## Which commands need what
40
+ | If the repo has… | `ship init` can add… |
41
+ |------------------|----------------------|
42
+ | `.cursor/` | Cursor rule **`.cursor/rules/ship-methodology-api.mdc`** |
43
+ | **`AGENTS.md`** | Appended section (Codex-style / generic agents file) |
44
+ | **`CLAUDE.md`** | Appended section |
45
+ | **`.codex/`** | **`SHIP_API.md`** under `.codex/` |
46
+ | **`.github/copilot-instructions.md`** | Appended section |
45
47
 
46
- | Command | Needs |
47
- |--------|--------|
48
- | `ship patterns …`, `ship tools …`, `ship workflows …`, `ship collections …` | Same **`SHIP_API_BASE`** as docs when not on disk. **Local:** cwd inside Ship or **`SHIP_REPO`**. |
49
- | `ship docs search|fetch|feedback` | **`SHIP_API_BASE`** (default `http://127.0.0.1:8100`) or `--base-url`. |
50
- | `ship init` | Target repo cwd; **`SHIP_API_BASE` / `--base-url`** is the API URL written into snippets. |
48
+ If **none** of the above exist, init offers a **standalone** **`SHIP_AGENT_API.md`** in the repo root so humans can copy the contract into whatever system you use later.
51
49
 
52
- ## Publishing (maintainers)
50
+ Use **`--only cursor|agents-md|claude-md|codex|copilot`** to limit targets; **`--cwd <dir>`** to point at another root.
53
51
 
54
- Releases are published via GitHub Actions (**Publish @elmundi/ship-cli to npm**): `npm publish -w @elmundi/ship-cli` from the monorepo root (not `npm publish --prefix cli`, which would try to publish the private root package). Configure the **`NPM_TOKEN`** repository secret. On npmjs.com use either a **Granular Access Token** with **Publish** on **`@elmundi/ship-cli`** (or the **elmundi** org) and **“Bypass two-factor authentication”** enabled for automation, or a classic **Automation** token — classic **Publish** tokens often cannot publish from CI when 2FA is on (`E403` *Two-factor authentication or granular access token with bypass 2fa…*).
52
+ ### 3. Apply with confirmation (recommended)
55
53
 
56
- The root monorepo `package.json` stays **`private`: true**; only **`@elmundi/ship-cli`** is intended for the public registry.
54
+ Interactive run prints the plan and asks **Apply these changes? [y/N]**:
57
55
 
58
- ## Semver
56
+ ```bash
57
+ npx @elmundi/ship-cli init
58
+ ```
59
+
60
+ After you confirm **`y`**, it writes/updates the files above. Injected content tells agents to **use the Ship methodology API** (and the **`ship`** CLI from a dev shell): **search → fetch** workflow, **`ship docs fetch`** for documentation paths, **`ship pattern|tool|workflow|collection`** for catalog entries, and **`ship docs feedback`** for safe retro notes — so methodology and **tools/workflows** discovery stay consistent with the server.
61
+
62
+ ### 4. Non-interactive (CI or scripts)
63
+
64
+ Only after you are happy with **`--dry-run`**:
65
+
66
+ ```bash
67
+ npx @elmundi/ship-cli init --yes
68
+ ```
69
+
70
+ **`--force`** replaces blocks that were already injected (same marker). Without **`--force`**, existing injections are skipped.
71
+
72
+ ## Commands (quick reference)
73
+
74
+ | Command | Role |
75
+ |--------|------|
76
+ | **`ship init`** | Inject agent-facing rules / sections with your **`SHIP_API_BASE`** (or **`--base-url`**). |
77
+ | **`ship search …`** | Vector search over methodology corpus (`POST /search`). |
78
+ | **`ship docs fetch …`**, **`ship docs feedback …`** | Documentation file fetch and retro feedback (`POST /fetch` with `path`, `POST /feedback`). |
79
+ | **`ship pattern|tool|workflow|collection`** **`list` \| `show` \| `fetch` \| `search`** | Catalogs; hosted mode uses the same API (including **`fetch`** via `POST /fetch` with `kind` + `id`). Plural aliases (`patterns`, `tools`, …) work. |
80
+
81
+ **Maintainers / full Ship checkout:** if the current directory (or **`SHIP_REPO`**) is inside the Ship monorepo, **`list` / `show` / `fetch`** for catalogs can read manifests from **disk** instead of HTTP. **`ship search`** always uses HTTP.
82
+
83
+ Run **`ship help`** for full usage.
84
+
85
+ ## Versioning (until the CLI stabilizes)
86
+
87
+ Releases **bump the patch (third) number only** (e.g. `0.8.0` → `0.8.1`) while the command surface and docs are still settling. Minor/major bumps resume once things are stable.
88
+
89
+ ## Publishing (maintainers)
59
90
 
60
- Package version lives in **`cli/package.json`**. Bump it for each npm release following [semver](https://semver.org/).
91
+ GitHub Action **Publish @elmundi/ship-cli to npm** (tag **`cli-v<version>`** must match **`cli/package.json`**, or use **workflow_dispatch**). Repository secret **`NPM_TOKEN`** required. Publish from monorepo root: **`npm publish -w @elmundi/ship-cli`**, not `npm publish --prefix cli` (root package is private).
package/bin/ship.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { extractGlobalArgv } from "../lib/config.mjs";
3
3
  import { docsCommand } from "../lib/commands/docs.mjs";
4
- import { patternsCommand } from "../lib/commands/patterns.mjs";
5
- import { manifestCatalogCommand } from "../lib/commands/manifest-catalog.mjs";
4
+ import { searchCommand } from "../lib/commands/search.mjs";
5
+ import { patternCommand } from "../lib/commands/patterns.mjs";
6
+ import { resourceManifestCommand } from "../lib/commands/manifest-catalog.mjs";
6
7
  import { printHelp } from "../lib/commands/help.mjs";
7
8
  import { initCommand } from "../lib/commands/init.mjs";
8
9
 
@@ -24,28 +25,33 @@ try {
24
25
  process.exit(0);
25
26
  }
26
27
 
28
+ if (cmd === "search") {
29
+ await searchCommand(ctx, rest);
30
+ process.exit(0);
31
+ }
32
+
27
33
  if (cmd === "docs") {
28
34
  await docsCommand(ctx, rest);
29
35
  process.exit(0);
30
36
  }
31
37
 
32
- if (cmd === "patterns") {
33
- await patternsCommand(ctx, rest);
38
+ if (cmd === "pattern" || cmd === "patterns") {
39
+ await patternCommand(ctx, rest);
34
40
  process.exit(0);
35
41
  }
36
42
 
37
- if (cmd === "tools") {
38
- await manifestCatalogCommand("tools", ctx, rest);
43
+ if (cmd === "tool" || cmd === "tools") {
44
+ await resourceManifestCommand("tool", ctx, rest);
39
45
  process.exit(0);
40
46
  }
41
47
 
42
- if (cmd === "workflows") {
43
- await manifestCatalogCommand("workflows", ctx, rest);
48
+ if (cmd === "workflow" || cmd === "workflows") {
49
+ await resourceManifestCommand("workflow", ctx, rest);
44
50
  process.exit(0);
45
51
  }
46
52
 
47
- if (cmd === "collections") {
48
- await manifestCatalogCommand("collections", ctx, rest);
53
+ if (cmd === "collection" || cmd === "collections") {
54
+ await resourceManifestCommand("collection", ctx, rest);
49
55
  process.exit(0);
50
56
  }
51
57
 
@@ -1,6 +1,7 @@
1
1
  import { apiPost } from "../http.mjs";
2
2
 
3
3
  /**
4
+ * Documentation files only: fetch markdown by repo path, retro feedback.
4
5
  * @param {{ baseUrl: string; json: boolean }} ctx
5
6
  * @param {string[]} args
6
7
  */
@@ -8,52 +9,27 @@ export async function docsCommand(ctx, args) {
8
9
  const [sub, ...rest] = args;
9
10
  if (!sub || sub === "help") {
10
11
  console.log(`Usage:
11
- ship docs search <query> [--top-k 8]
12
12
  ship docs fetch <repo-relative-path>
13
13
  ship docs feedback --title "..." --summary "..." [--recommendation "line"]... [--source-context "..."]
14
14
 
15
- Global flags: --base-url URL --json`);
16
- return;
17
- }
15
+ Vector search: ship search <query>
16
+ Catalog bodies: ship pattern|tool|workflow|collection fetch <id>
18
17
 
19
- if (sub === "search") {
20
- const qParts = [];
21
- let topK = 8;
22
- for (let i = 0; i < rest.length; i++) {
23
- const a = rest[i];
24
- if (a === "--top-k" && rest[i + 1]) {
25
- topK = Number(rest[++i]);
26
- continue;
27
- }
28
- qParts.push(a);
29
- }
30
- const query = qParts.join(" ").trim();
31
- if (query.length < 3) {
32
- console.error("search: query must be at least 3 characters.");
33
- process.exit(1);
34
- }
35
- const data = await apiPost(ctx.baseUrl, "/search", { query, top_k: topK });
36
- if (ctx.json) console.log(JSON.stringify(data, null, 2));
37
- else {
38
- console.log(`Query: ${data.query}\n`);
39
- for (const r of data.results || []) {
40
- console.log(`- ${r.path} (chunk ${r.chunk_index}, distance ${r.distance ?? "n/a"})`);
41
- console.log(` ${r.snippet}\n`);
42
- }
43
- }
18
+ Global flags: --base-url URL --json`);
44
19
  return;
45
20
  }
46
21
 
47
22
  if (sub === "fetch") {
48
23
  const p = rest.join(" ").trim();
49
24
  if (!p) {
50
- console.error("fetch: path required.");
25
+ console.error("fetch: repo-relative path required (markdown/text under the Ship tree).");
51
26
  process.exit(1);
52
27
  }
53
28
  const data = await apiPost(ctx.baseUrl, "/fetch", { path: p });
54
29
  if (ctx.json) console.log(JSON.stringify(data, null, 2));
55
30
  else {
56
- console.log(`# ${data.path}\n`);
31
+ const title = data.path ?? data.id ?? "document";
32
+ console.log(`# ${title}\n`);
57
33
  console.log(data.content);
58
34
  }
59
35
  return;
@@ -1,59 +1,39 @@
1
1
  export function printHelp() {
2
- console.log(`Ship CLI — Ship methodology HTTP API (search, fetch, feedback, patterns, tools, workflows, collections) + init.
2
+ console.log(`Ship CLI — methodology on ship.elmundi.com (or SHIP_API_BASE) + init.
3
3
 
4
- USAGE
4
+ ONE FLOW
5
+ 1) ship search <query> — vector search (POST /search) over docs + prompts + README
6
+ 2) ship docs fetch <path> — full markdown file by repo-relative path (POST /fetch { path })
7
+ ship pattern|tool|workflow|collection fetch <id> — catalog entry body (POST /fetch { kind, id })
8
+ 3) ship docs feedback … — improvement / retro note (POST /feedback)
9
+
10
+ COMMANDS
5
11
  ship help
6
- ship docs search <query> [--top-k N]
7
- ship docs fetch <path>
12
+ ship search <query> [--top-k N]
13
+
14
+ ship docs fetch <repo-relative-path>
8
15
  ship docs feedback --title "..." --summary "..." [--recommendation "…"]... [--source-context "…"]
9
- ship patterns list
10
- ship patterns show <pattern-id>
11
- ship tools list | ship tools show <id>
12
- ship workflows list | ship workflows show <id>
13
- ship collections list | ship collections show <id>
16
+
17
+ ship pattern list | ship pattern show <id> | ship pattern fetch <id> | ship pattern search <query> [--top-k N]
18
+ ship tool | ship workflow … | ship collection … (same subcommands; plural aliases: patterns, tools, …)
19
+
14
20
  ship init [--yes] [--force] [--dry-run] [--only <id>] [--cwd <dir>]
15
21
 
16
22
  GLOBAL FLAGS
17
- --base-url URL API root for ALL HTTP commands (default: SHIP_API_BASE or http://127.0.0.1:8100)
18
- --json Machine-readable JSON to stdout
23
+ --base-url URL Methodology API (default: SHIP_API_BASE or https://ship.elmundi.com/api/methodology)
24
+ --json Machine-readable JSON
19
25
 
20
- CATALOG COMMANDS (patterns, tools, workflows, collections)
21
- If cwd or SHIP_REPO points at a Ship clone: read manifests from disk.
22
- Otherwise: same base URL as docs — GET /patterns, /tools, /workflows, /collections (and /{id} for show).
26
+ LOCAL TREE
27
+ pattern / tool / workflow / collection list|show|fetch read manifests from disk when cwd or SHIP_REPO
28
+ is inside the Ship monorepo (search always uses HTTP).
23
29
 
24
30
  INIT FLAGS
25
- --yes Skip confirmation prompts (non-interactive; writes files — review plan with --dry-run first)
26
- --force Overwrite / replace existing ship-cli blocks
27
- --dry-run Print actions only
28
- --only <id> Limit to one target: cursor | agents-md | claude-md | codex | copilot
29
- --cwd <dir> Repository root (default: current directory)
30
-
31
- BACKEND
32
- Start from Ship repo: uvicorn backend.app.main:app --reload --host 127.0.0.1 --port 8100
33
- Env on server: OPENAI_API_KEY (/search), GITHUB_TOKEN (/feedback)
34
-
35
- ────────────────────────────────────────────────────────
36
- Embedding in an agent (Cursor, Codex, Claude Code, etc.)
37
- ────────────────────────────────────────────────────────
38
-
39
- 1. Run the Ship backend locally (or deploy it) and set SHIP_API_BASE in the agent environment
40
- if the URL is not the default http://127.0.0.1:8100 .
41
-
42
- 2. Teach the agent this loop (or mirror it with curl):
43
- - POST /search with the user question → pick 1–3 paths from results (CLI: ship docs search)
44
- - POST /fetch for each chosen path → ground answers in those files (CLI: ship docs fetch)
45
- - Optionally list/show patterns (CLI: ship patterns list | ship patterns show <id> — GET /patterns on the same API, or disk in a Ship clone / SHIP_REPO)
46
- - POST /feedback only for retro-style notes (no secrets in free text) (CLI: ship docs feedback)
47
-
48
- 3. Run ship init in the TARGET repository (your product repo, not necessarily Ship).
49
- It detects .cursor/, AGENTS.md, CLAUDE.md, .codex/, or Copilot instructions and, after
50
- your confirmation, drops a focused rule or appends a markdown section the agent can read.
51
-
52
- 4. List patterns/tools/workflows/collections via the same API (or from disk in a clone / SHIP_REPO): ship patterns list , ship tools list , etc.
53
-
54
- 5. From CI or headless agents, call the same HTTP API with curl or fetch; use ship … --json
55
- for stable parsing.
31
+ --yes Non-interactive apply (use --dry-run first)
32
+ --force Replace existing injected blocks
33
+ --dry-run Preview only
34
+ --only cursor | agents-md | claude-md | codex | copilot
35
+ --cwd Target repo root
56
36
 
57
- For full request/response schemas see documentation/tools/backend-api.md in the Ship repo.
37
+ For HTTP schemas see documentation/tools/backend-api.md in the Ship repo.
58
38
  `);
59
39
  }
@@ -17,7 +17,7 @@ export async function initCommand(ctx, args) {
17
17
  ship init [--yes] [--force] [--dry-run] [--only <id>] [--cwd <dir>]
18
18
 
19
19
  Writes Cursor rules and/or markdown sections that point agents at the Ship methodology API
20
- (base URL from SHIP_API_BASE or --base-url, default http://127.0.0.1:8100).
20
+ (base URL from SHIP_API_BASE or --base-url; same default as other commands, e.g. ship.elmundi.com).
21
21
 
22
22
  Flags:
23
23
  --dry-run Show the plan only (recommended before first use).
@@ -1,62 +1,80 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { apiGet } from "../http.mjs";
3
+ import { apiGet, apiPost } from "../http.mjs";
4
4
  import { resolveShipRepoRootForCatalog } from "../find-ship-root.mjs";
5
+ import { searchCommand } from "./search.mjs";
5
6
 
6
- /** @type {Record<string, { manifestRel: string; arrayKey: string; name: string }>} */
7
- const CATALOGS = {
8
- tools: { manifestRel: "tools/manifest.json", arrayKey: "tools", name: "Tools" },
9
- workflows: {
7
+ /** @type {Record<string, { manifestRel: string; arrayKey: string; name: string; apiPath: string; fetchKind: string }>} */
8
+ const RESOURCES = {
9
+ tool: {
10
+ manifestRel: "tools/manifest.json",
11
+ arrayKey: "tools",
12
+ name: "Tools",
13
+ apiPath: "tools",
14
+ fetchKind: "tool",
15
+ },
16
+ workflow: {
10
17
  manifestRel: "workflows/manifest.json",
11
18
  arrayKey: "workflows",
12
19
  name: "Workflows",
20
+ apiPath: "workflows",
21
+ fetchKind: "workflow",
13
22
  },
14
- collections: {
23
+ collection: {
15
24
  manifestRel: "collections/manifest.json",
16
25
  arrayKey: "collections",
17
26
  name: "Collections",
27
+ apiPath: "collections",
28
+ fetchKind: "collection",
18
29
  },
19
30
  };
20
31
 
21
32
  /**
22
- * @param {"tools"|"workflows"|"collections"} kind
33
+ * @param {"tool"|"workflow"|"collection"} resource
23
34
  * @param {{ baseUrl: string; json: boolean }} ctx
24
- * @param {string[]} args subcommand tail (e.g. `list` or `show`, `linear`)
35
+ * @param {string[]} args
25
36
  */
26
- export async function manifestCatalogCommand(kind, ctx, args) {
27
- const spec = CATALOGS[kind];
28
- if (!spec) throw new Error(`Unknown catalog kind: ${kind}`);
37
+ export async function resourceManifestCommand(resource, ctx, args) {
38
+ const spec = RESOURCES[resource];
39
+ if (!spec) throw new Error(`Unknown resource: ${resource}`);
29
40
 
30
41
  const [sub, ...rest] = args;
31
42
  if (!sub || sub === "help") {
32
43
  console.log(`Usage:
33
- ship ${kind} list
34
- ship ${kind} show <id>
44
+ ship ${resource} list
45
+ ship ${resource} show <id>
46
+ ship ${resource} fetch <id>
47
+ ship ${resource} search <query> [--top-k N]
35
48
 
36
49
  With a local Ship tree (cwd or SHIP_REPO): reads ${spec.manifestRel} on disk.
37
- Otherwise: methodology HTTP API GET /${kind} and GET /${kind}/<id> (SHIP_API_BASE / --base-url).
50
+ Otherwise: methodology API (GET /${spec.apiPath}, POST /fetch for fetch, POST /search for search).
51
+
52
+ Plural alias: ship ${spec.apiPath} …
38
53
 
39
54
  Global flags: --base-url URL --json`);
40
55
  return;
41
56
  }
42
57
 
58
+ if (sub === "search") {
59
+ await searchCommand(ctx, rest);
60
+ return;
61
+ }
62
+
43
63
  const root = resolveShipRepoRootForCatalog();
44
64
  if (root) {
45
- await manifestFromDisk(kind, root, spec, ctx, sub, rest);
65
+ await manifestFromDisk(resource, root, spec, ctx, sub, rest);
46
66
  } else {
47
- await manifestFromHosted(kind, spec, ctx, sub, rest);
67
+ await manifestFromHosted(resource, spec, ctx, sub, rest);
48
68
  }
49
69
  }
50
70
 
51
71
  /**
52
- * @param {"tools"|"workflows"|"collections"} kind
53
- * @param {typeof CATALOGS["tools"]} spec
54
- * @param {{ baseUrl: string; json: boolean }} ctx
72
+ * @param {"tool"|"workflow"|"collection"} resource
55
73
  */
56
- async function manifestFromHosted(kind, spec, ctx, sub, rest) {
74
+ async function manifestFromHosted(resource, spec, ctx, sub, rest) {
57
75
  const base = ctx.baseUrl;
58
76
  if (sub === "list") {
59
- const data = await apiGet(base, `/${kind}`);
77
+ const data = await apiGet(base, `/${spec.apiPath}`);
60
78
  if (ctx.json) {
61
79
  console.log(JSON.stringify(data, null, 2));
62
80
  } else {
@@ -78,7 +96,22 @@ async function manifestFromHosted(kind, spec, ctx, sub, rest) {
78
96
  console.error("show: id required.");
79
97
  process.exit(1);
80
98
  }
81
- const data = await apiGet(base, `/${kind}/${encodeURIComponent(id)}`);
99
+ const data = await apiGet(base, `/${spec.apiPath}/${encodeURIComponent(id)}`);
100
+ if (ctx.json) {
101
+ console.log(JSON.stringify(data, null, 2));
102
+ } else {
103
+ console.log(`# ${data.title} (${data.id})\n`);
104
+ console.log(data.content);
105
+ }
106
+ return;
107
+ }
108
+ if (sub === "fetch") {
109
+ const id = rest[0];
110
+ if (!id) {
111
+ console.error("fetch: id required.");
112
+ process.exit(1);
113
+ }
114
+ const data = await apiPost(base, "/fetch", { kind: spec.fetchKind, id });
82
115
  if (ctx.json) {
83
116
  console.log(JSON.stringify(data, null, 2));
84
117
  } else {
@@ -87,17 +120,14 @@ async function manifestFromHosted(kind, spec, ctx, sub, rest) {
87
120
  }
88
121
  return;
89
122
  }
90
- console.error(`Unknown ${kind} subcommand: ${sub}`);
123
+ console.error(`Unknown ${resource} subcommand: ${sub}`);
91
124
  process.exit(1);
92
125
  }
93
126
 
94
127
  /**
95
- * @param {"tools"|"workflows"|"collections"} kind
96
- * @param {string} root
97
- * @param {typeof CATALOGS["tools"]} spec
98
- * @param {{ json: boolean }} ctx
128
+ * @param {"tool"|"workflow"|"collection"} resource
99
129
  */
100
- async function manifestFromDisk(kind, root, spec, ctx, sub, rest) {
130
+ async function manifestFromDisk(resource, root, spec, ctx, sub, rest) {
101
131
  const manifestPath = path.join(root, spec.manifestRel);
102
132
  const raw = fs.readFileSync(manifestPath, "utf8");
103
133
  /** @type {Record<string, unknown>} */
@@ -120,10 +150,10 @@ async function manifestFromDisk(kind, root, spec, ctx, sub, rest) {
120
150
  return;
121
151
  }
122
152
 
123
- if (sub === "show") {
153
+ if (sub === "show" || sub === "fetch") {
124
154
  const id = rest[0];
125
155
  if (!id) {
126
- console.error("show: id required.");
156
+ console.error(`${sub}: id required.`);
127
157
  process.exit(1);
128
158
  }
129
159
  const entry = entries.find((e) => e.id === id);
@@ -152,6 +182,6 @@ async function manifestFromDisk(kind, root, spec, ctx, sub, rest) {
152
182
  return;
153
183
  }
154
184
 
155
- console.error(`Unknown ${kind} subcommand: ${sub}`);
185
+ console.error(`Unknown ${resource} subcommand: ${sub}`);
156
186
  process.exit(1);
157
187
  }
@@ -1,7 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { apiGet } from "../http.mjs";
3
+ import { apiGet, apiPost } from "../http.mjs";
4
4
  import { resolveShipRepoRootForCatalog } from "../find-ship-root.mjs";
5
+ import { searchCommand } from "./search.mjs";
5
6
 
6
7
  const MANIFEST_REL = "patterns/manifest.json";
7
8
 
@@ -36,7 +37,7 @@ function slimEntry(p) {
36
37
 
37
38
  /**
38
39
  * @param {string} root
39
- * @param {{ json: boolean }} ctx
40
+ * @param {{ baseUrl: string; json: boolean }} ctx
40
41
  * @param {string} sub
41
42
  * @param {string[]} rest
42
43
  */
@@ -64,10 +65,10 @@ async function patternsFromDisk(root, ctx, sub, rest) {
64
65
  return;
65
66
  }
66
67
 
67
- if (sub === "show") {
68
+ if (sub === "show" || sub === "fetch") {
68
69
  const id = rest[0];
69
70
  if (!id) {
70
- console.error("show: pattern id required.");
71
+ console.error(`${sub}: pattern id required.`);
71
72
  process.exit(1);
72
73
  }
73
74
  const entry = entries.find((e) => e.id === id);
@@ -101,7 +102,7 @@ async function patternsFromDisk(root, ctx, sub, rest) {
101
102
  return;
102
103
  }
103
104
 
104
- console.error(`Unknown patterns subcommand: ${sub}`);
105
+ console.error(`Unknown pattern subcommand: ${sub}`);
105
106
  process.exit(1);
106
107
  }
107
108
 
@@ -139,7 +140,21 @@ async function patternsFromHosted(ctx, sub, rest) {
139
140
  }
140
141
  return;
141
142
  }
142
- console.error(`Unknown patterns subcommand: ${sub}`);
143
+ if (sub === "fetch") {
144
+ const id = rest[0];
145
+ if (!id) {
146
+ console.error("fetch: pattern id required.");
147
+ process.exit(1);
148
+ }
149
+ const data = await apiPost(base, "/fetch", { kind: "pattern", id });
150
+ if (ctx.json) console.log(JSON.stringify(data, null, 2));
151
+ else {
152
+ console.log(`# ${data.title} (${data.id})\n`);
153
+ console.log(data.content);
154
+ }
155
+ return;
156
+ }
157
+ console.error(`Unknown pattern subcommand: ${sub}`);
143
158
  process.exit(1);
144
159
  }
145
160
 
@@ -147,20 +162,29 @@ async function patternsFromHosted(ctx, sub, rest) {
147
162
  * @param {{ baseUrl: string; json: boolean }} ctx
148
163
  * @param {string[]} args
149
164
  */
150
- export async function patternsCommand(ctx, args) {
165
+ export async function patternCommand(ctx, args) {
151
166
  const [sub, ...rest] = args;
152
167
  if (!sub || sub === "help") {
153
168
  console.log(`Usage:
154
- ship patterns list
155
- ship patterns show <pattern-id>
169
+ ship pattern list
170
+ ship pattern show <id>
171
+ ship pattern fetch <id>
172
+ ship pattern search <query> [--top-k N]
156
173
 
157
- With a local Ship tree (cwd or SHIP_REPO): reads patterns/manifest.json on disk.
158
- Otherwise: same HTTP API as methodology — GET /patterns and GET /patterns/{id} (SHIP_API_BASE / --base-url).
174
+ With a local Ship tree (cwd or SHIP_REPO): list/show/fetch read patterns/manifest.json on disk.
175
+ Otherwise: methodology API (GET /patterns, POST /fetch for fetch, POST /search for search).
176
+
177
+ Plural alias: ship patterns …
159
178
 
160
179
  Global flags: --base-url URL --json`);
161
180
  return;
162
181
  }
163
182
 
183
+ if (sub === "search") {
184
+ await searchCommand(ctx, rest);
185
+ return;
186
+ }
187
+
164
188
  const root = resolveShipRepoRootForCatalog();
165
189
  if (root) {
166
190
  await patternsFromDisk(root, ctx, sub, rest);
@@ -0,0 +1,43 @@
1
+ import { apiPost } from "../http.mjs";
2
+
3
+ /**
4
+ * Vector search over methodology corpus (documentation, prompts, README).
5
+ * @param {{ baseUrl: string; json: boolean }} ctx
6
+ * @param {string[]} args query tokens (same line as `ship search …`)
7
+ */
8
+ export async function searchCommand(ctx, args) {
9
+ if (!args.length || args[0] === "help" || args[0] === "-h" || args[0] === "--help") {
10
+ console.log(`Usage:
11
+ ship search <query> [--top-k 8]
12
+
13
+ POST /search on the methodology API (same SHIP_API_BASE as ship pattern/tool/…).
14
+
15
+ Global flags: --base-url URL --json`);
16
+ return;
17
+ }
18
+
19
+ const qParts = [];
20
+ let topK = 8;
21
+ for (let i = 0; i < args.length; i++) {
22
+ const a = args[i];
23
+ if (a === "--top-k" && args[i + 1]) {
24
+ topK = Number(args[++i]);
25
+ continue;
26
+ }
27
+ qParts.push(a);
28
+ }
29
+ const query = qParts.join(" ").trim();
30
+ if (query.length < 3) {
31
+ console.error("search: query must be at least 3 characters.");
32
+ process.exit(1);
33
+ }
34
+ const data = await apiPost(ctx.baseUrl, "/search", { query, top_k: topK });
35
+ if (ctx.json) console.log(JSON.stringify(data, null, 2));
36
+ else {
37
+ console.log(`Query: ${data.query}\n`);
38
+ for (const r of data.results || []) {
39
+ console.log(`- ${r.path} (chunk ${r.chunk_index}, distance ${r.distance ?? "n/a"})`);
40
+ console.log(` ${r.snippet}\n`);
41
+ }
42
+ }
43
+ }
package/lib/config.mjs CHANGED
@@ -5,7 +5,9 @@
5
5
  export function extractGlobalArgv(argv) {
6
6
  const out = {
7
7
  _: /** @type {string[]} */ ([]),
8
- baseUrl: (process.env.SHIP_API_BASE || "http://127.0.0.1:8100").replace(/\/$/, ""),
8
+ baseUrl: (
9
+ process.env.SHIP_API_BASE || "https://ship.elmundi.com/api/methodology"
10
+ ).replace(/\/$/, ""),
9
11
  json: false,
10
12
  yes: false,
11
13
  force: false,
package/lib/templates.mjs CHANGED
@@ -20,14 +20,14 @@ Base URL (override with \`SHIP_API_BASE\` for agents, or \`--base-url\` for CLI)
20
20
  1. **Discover** — \`POST /search\` with a natural-language query over Ship docs + prompts.
21
21
  2. **Read** — \`POST /fetch\` with a repo-relative \`path\` from search hits (markdown/text only).
22
22
  3. **Retro** — \`POST /feedback\` to open a sanitized GitHub issue (needs \`GITHUB_TOKEN\` on the server).
23
- 4. **Patterns** — run \`ship patterns list\` / \`ship patterns show <id>\` (\`GET /patterns\` on the same base URL as search; or disk when cwd/\`SHIP_REPO\` is the Ship tree).
23
+ 4. **Catalog** — \`ship pattern|tool|workflow|collection list\` / \`show <id>\` / \`fetch <id>\` (\`GET /…\` on the same base URL as search, or \`POST /fetch\` with \`{ kind, id }\` when hosted; or disk when cwd/\`SHIP_REPO\` is the Ship tree).
24
24
 
25
25
  ## Examples (CLI from Ship repo)
26
26
 
27
27
  \`\`\`bash
28
- npm run ship -- docs search "release gates and qa split" --top-k 8
28
+ npm run ship -- search "release gates and qa split" --top-k 8
29
29
  npm run ship -- docs fetch documentation/adoption/delivery-quality-and-release-process.md
30
- npm run ship -- patterns list
30
+ npm run ship -- pattern list
31
31
  \`\`\`
32
32
 
33
33
  Equivalent curl (when not using the CLI):
@@ -65,7 +65,7 @@ Base URL: \`${baseUrl}\` (env \`SHIP_API_BASE\`).
65
65
  - **Search** \`POST /search\` JSON \`{ "query": string, "top_k"?: number }\`
66
66
  - **Fetch** \`POST /fetch\` JSON \`{ "path": "documentation/...md" }\`
67
67
  - **Feedback** \`POST /feedback\` JSON \`{ "title", "summary", "recommendations"?: string[], "source_context"?: string }\`
68
- - **Patterns** — \`ship patterns list\` / \`ship patterns show <id>\` (same \`SHIP_API_BASE\` as search, or local tree): \`GET /patterns\`, \`GET /patterns/{id}\`
68
+ - **Catalog** — \`ship pattern|tool|workflow|collection list\` / \`show <id>\` / \`fetch <id>\` (same \`SHIP_API_BASE\` as search, or local tree): \`GET /patterns\`, \`GET /patterns/{id}\`, or \`POST /fetch\` with \`{ "kind": "pattern", "id": "…" }\`
69
69
 
70
70
  Use search first, then fetch the best path. Keep tokens out of feedback bodies.
71
71
  `;
@@ -90,15 +90,16 @@ See the Ship repo \`documentation/tools/backend-api.md\` for full contract.
90
90
  | POST | /search | \`{ "query": "...", "top_k": 8 }\` |
91
91
  | POST | /fetch | \`{ "path": "documentation/foo.md" }\` |
92
92
  | POST | /feedback | \`{ "title", "summary", "recommendations": [], "source_context" }\` |
93
- | GET | /patterns | list manifest — **CLI:** \`ship patterns list\` (HTTP or disk in clone) |
94
- | GET | /patterns/{id} | metadata + markdown \`content\` — **CLI:** \`ship patterns show <id>\` |
93
+ | GET | /patterns | list manifest — **CLI:** \`ship pattern list\` (HTTP or disk in clone) |
94
+ | GET | /patterns/{id} | metadata + markdown \`content\` — **CLI:** \`ship pattern show <id>\` |
95
+ | POST | /fetch | catalog body — **CLI:** \`ship pattern fetch <id>\` with \`{ "kind": "pattern", "id" }\` |
95
96
 
96
97
  ## CLI (from Ship monorepo)
97
98
 
98
99
  \`\`\`bash
99
- npm run ship -- patterns list
100
- npm run ship -- patterns show catalog-a1-intake
101
- npm run ship -- docs search "intake labels" --top-k 5
100
+ npm run ship -- pattern list
101
+ npm run ship -- pattern show catalog-a1-intake
102
+ npm run ship -- search "intake labels" --top-k 5
102
103
  \`\`\`
103
104
 
104
105
  ## curl (direct HTTP)
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@elmundi/ship-cli",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "type": "module",
5
- "description": "Ship CLI — docs API (search/fetch/feedback), on-disk patterns & catalogs, and agent init",
5
+ "description": "Ship CLI — methodology search, docs fetch/feedback, catalog commands (pattern/tool/workflow/collection), and agent init",
6
6
  "license": "Apache-2.0",
7
7
  "author": "Denys Kuzin",
8
8
  "repository": {