@elmundi/ship-cli 0.7.0 → 0.8.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 CHANGED
@@ -26,8 +26,8 @@ From a full **Ship** monorepo clone you can still run `npm run ship -- …` from
26
26
  2. From **any** directory, point **`SHIP_API_BASE`** at the **deployed methodology API** and list patterns or catalogs (same server as search):
27
27
 
28
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
29
+ SHIP_API_BASE=https://your-ship-api.example.com npx @elmundi/ship-cli pattern list
30
+ SHIP_API_BASE=https://your-ship-api.example.com npx @elmundi/ship-cli tool list
31
31
  ```
32
32
 
33
33
  3. Optional: work from a **local** Ship checkout (or **`SHIP_REPO`**) to read manifests from disk without calling the API.
@@ -45,8 +45,8 @@ From a full **Ship** monorepo clone you can still run `npm run ship -- …` from
45
45
 
46
46
  | Command | Needs |
47
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`. |
48
+ | `ship pattern|tool|workflow|collection …` (plural aliases) | Same **`SHIP_API_BASE`** as search/docs when not on disk. **Local:** cwd inside Ship or **`SHIP_REPO`**. |
49
+ | `ship search`, `ship docs fetch|feedback` | **`SHIP_API_BASE`** (default public methodology URL; override locally) or `--base-url`. |
50
50
  | `ship init` | Target repo cwd; **`SHIP_API_BASE` / `--base-url`** is the API URL written into snippets. |
51
51
 
52
52
  ## Publishing (maintainers)
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
  }
@@ -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.0",
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": {