@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 +4 -4
- package/bin/ship.mjs +16 -10
- package/lib/commands/docs.mjs +7 -31
- package/lib/commands/help.mjs +26 -46
- package/lib/commands/manifest-catalog.mjs +61 -31
- package/lib/commands/patterns.mjs +35 -11
- package/lib/commands/search.mjs +43 -0
- package/lib/config.mjs +3 -1
- package/lib/templates.mjs +10 -9
- package/package.json +2 -2
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
|
|
30
|
-
SHIP_API_BASE=https://your-ship-api.example.com npx @elmundi/ship-cli
|
|
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
|
|
49
|
-
| `ship docs
|
|
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 {
|
|
5
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
53
|
+
if (cmd === "collection" || cmd === "collections") {
|
|
54
|
+
await resourceManifestCommand("collection", ctx, rest);
|
|
49
55
|
process.exit(0);
|
|
50
56
|
}
|
|
51
57
|
|
package/lib/commands/docs.mjs
CHANGED
|
@@ -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
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
15
|
+
Vector search: ship search <query>
|
|
16
|
+
Catalog bodies: ship pattern|tool|workflow|collection fetch <id>
|
|
18
17
|
|
|
19
|
-
|
|
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
|
-
|
|
31
|
+
const title = data.path ?? data.id ?? "document";
|
|
32
|
+
console.log(`# ${title}\n`);
|
|
57
33
|
console.log(data.content);
|
|
58
34
|
}
|
|
59
35
|
return;
|
package/lib/commands/help.mjs
CHANGED
|
@@ -1,59 +1,39 @@
|
|
|
1
1
|
export function printHelp() {
|
|
2
|
-
console.log(`Ship CLI —
|
|
2
|
+
console.log(`Ship CLI — methodology on ship.elmundi.com (or SHIP_API_BASE) + init.
|
|
3
3
|
|
|
4
|
-
|
|
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
|
|
7
|
-
|
|
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
|
-
|
|
10
|
-
ship
|
|
11
|
-
ship
|
|
12
|
-
|
|
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
|
|
18
|
-
--json Machine-readable JSON
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
26
|
-
--force
|
|
27
|
-
--dry-run
|
|
28
|
-
--only
|
|
29
|
-
--cwd
|
|
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
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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 {"
|
|
33
|
+
* @param {"tool"|"workflow"|"collection"} resource
|
|
23
34
|
* @param {{ baseUrl: string; json: boolean }} ctx
|
|
24
|
-
* @param {string[]} args
|
|
35
|
+
* @param {string[]} args
|
|
25
36
|
*/
|
|
26
|
-
export async function
|
|
27
|
-
const spec =
|
|
28
|
-
if (!spec) throw new Error(`Unknown
|
|
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 ${
|
|
34
|
-
ship ${
|
|
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
|
|
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(
|
|
65
|
+
await manifestFromDisk(resource, root, spec, ctx, sub, rest);
|
|
46
66
|
} else {
|
|
47
|
-
await manifestFromHosted(
|
|
67
|
+
await manifestFromHosted(resource, spec, ctx, sub, rest);
|
|
48
68
|
}
|
|
49
69
|
}
|
|
50
70
|
|
|
51
71
|
/**
|
|
52
|
-
* @param {"
|
|
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(
|
|
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, `/${
|
|
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, `/${
|
|
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 ${
|
|
123
|
+
console.error(`Unknown ${resource} subcommand: ${sub}`);
|
|
91
124
|
process.exit(1);
|
|
92
125
|
}
|
|
93
126
|
|
|
94
127
|
/**
|
|
95
|
-
* @param {"
|
|
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(
|
|
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(
|
|
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 ${
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
155
|
-
ship
|
|
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):
|
|
158
|
-
Otherwise:
|
|
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: (
|
|
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. **
|
|
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 --
|
|
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 --
|
|
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
|
-
- **
|
|
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
|
|
94
|
-
| GET | /patterns/{id} | metadata + markdown \`content\` — **CLI:** \`ship
|
|
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 --
|
|
100
|
-
npm run ship --
|
|
101
|
-
npm run ship --
|
|
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.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Ship CLI — docs
|
|
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": {
|