storymaps 1.2.7 → 1.2.9

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
@@ -23,6 +23,12 @@ Story maps are structured data. The CLI lets you export the structure behind the
23
23
  npm install -g storymaps
24
24
  ```
25
25
 
26
+ Or run it without installing, the way the Build with AI command on storymaps.io does:
27
+
28
+ ```bash
29
+ npx storymaps pull storymaps.io/abc123
30
+ ```
31
+
26
32
  Or clone and link locally:
27
33
 
28
34
  ```bash
@@ -43,6 +49,7 @@ Or pull an existing map:
43
49
 
44
50
  ```bash
45
51
  storymaps pull storymaps.io/abc123 # download as YAML
52
+ storymaps pull storymaps.io/p/k8f2m9xq/abc123 # a map inside a project link works too
46
53
  ```
47
54
 
48
55
  Edit the YAML, then push it back:
@@ -104,17 +111,19 @@ storymaps open storymaps.io/abc123 # open a specific remote map
104
111
  ```bash
105
112
  storymaps pull # uses id/site from storymap.yml
106
113
  storymaps pull storymaps.io/abc123 # pull a specific map
114
+ storymaps pull --force # overwrite the local file without asking
107
115
  ```
108
116
 
109
- Shows a summary of changes before overwriting.
117
+ Shows a summary of changes and asks before overwriting the local file, unless you pass `--force`.
110
118
 
111
119
  ### `push`
112
120
 
113
121
  ```bash
114
122
  storymaps push # push local changes to the server
123
+ storymaps push --force # skip the conflict check and overwrite the server copy
115
124
  ```
116
125
 
117
- If the file has no `id`, a new map is created on the server. Conflicts are detected using ETags. If the remote has changed since your last pull, you'll be prompted to pull first. Locked maps prompt for the password.
126
+ If the file has no `id`, a new map is created on the server. Conflicts are detected using ETags. If the remote has changed since your last pull, you'll be prompted to pull first; `--force` pushes anyway and replaces whatever changed on the server. Locked maps prompt for the password.
118
127
 
119
128
  ### `diff`
120
129
 
@@ -156,6 +165,12 @@ storymaps unlock --remove # permanently remove the lock
156
165
  storymaps validate storymap.yml # check YAML structure
157
166
  ```
158
167
 
168
+ ### Options
169
+
170
+ A target can be a map ID, `domain/id`, or a full URL, including a map inside a project link (`storymaps.io/p/<project>/<map>`).
171
+
172
+ `pull`, `push`, `open`, `lock`, `unlock`, `status` and `log` take `-f, --file <path>` to use a file other than `storymap.yml`.
173
+
159
174
  ### `convert`
160
175
 
161
176
  ```bash
@@ -259,6 +274,16 @@ Cards support: `name`, `body`, `color`, `status` (done / in-progress / planned /
259
274
 
260
275
  Slices support: `name`, `stories`, `goal` (up to 200 characters), `targetDate` (`YYYY-MM-DD`), and `collapsed` with `closedReason` (complete / closed).
261
276
 
277
+ ## Use with AI Coding Agents
278
+
279
+ A story map makes a good specification for an AI coding agent: the YAML lists every step of the journey and the cards under each one, slice by slice. **Build with AI** on storymaps.io writes the command for you:
280
+
281
+ ```bash
282
+ npx storymaps pull --force storymaps.io/abc123 && claude "The storymap file @storymap.yml describes a product plan in user story map format. Use it as a structured specification. Your task: build a functional prototype of the features listed in the 'MVP' slice."
283
+ ```
284
+
285
+ From there you steer the agent a slice or a feature at a time ("now build search from the V1 slice"). When a feature has been through QA, set its cards to `status: done` in `storymap.yml` and `storymaps push`, so the map shows the progress to everyone else. Pull again before the next round to pick up changes made in the browser.
286
+
262
287
  ## Self-Hosting
263
288
 
264
289
  The CLI works with any storymaps.io instance:
@@ -269,6 +294,8 @@ storymaps init --site storymaps.yourcompany.com
269
294
 
270
295
  See the [main project README](https://github.com/jackgleeson/storymaps.io#self-hosting) for setup instructions.
271
296
 
297
+ Every request names the CLI and its version in the User-Agent header (`storymaps-cli/<version>`), so CLI use shows up as such in your server logs.
298
+
272
299
  ## Development
273
300
 
274
301
  ```bash
@@ -283,7 +310,7 @@ To try a change against a local storymaps.io server, run the CLI from the repo,
283
310
  ### Releasing
284
311
 
285
312
  1. Commit your changes on `main`.
286
- 2. Bump the version in `package.json` and commit it as "Bump version to x.y.z". Use a patch for fixes and a minor version for new features.
313
+ 2. Bump the version in `package.json` and in both places near the top of `package-lock.json`, and commit it as "Bump version to x.y.z". Use a patch for fixes and a minor version for new features.
287
314
  3. Tag the commit and push both:
288
315
  ```bash
289
316
  git tag vx.y.z
package/lib/util/api.js CHANGED
@@ -1,6 +1,14 @@
1
1
  // Storymaps CLI — AGPL-3.0 — see LICENCE for details
2
2
  // HTTP client for storymaps.io API (uses Node 18+ built-in fetch)
3
3
 
4
+ import { readFileSync } from 'node:fs';
5
+
6
+ // Every request names the CLI and its version, so the server can tell CLI use from other scripts
7
+ const pkg = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf-8'));
8
+ export const USER_AGENT = `storymaps-cli/${pkg.version}`;
9
+
10
+ const request = (url, opts = {}) => fetch(url, { ...opts, headers: { 'User-Agent': USER_AGENT, ...opts.headers } });
11
+
4
12
  class ApiError extends Error {
5
13
  constructor(message, status, { conflict = false } = {}) {
6
14
  super(message);
@@ -32,7 +40,7 @@ async function handleResponse(res) {
32
40
  }
33
41
 
34
42
  export async function getMap(baseUrl, id, format = 'yaml') {
35
- const res = await fetch(`${baseUrl}/api/maps/${id}?format=${format}`);
43
+ const res = await request(`${baseUrl}/api/maps/${id}?format=${format}`);
36
44
  await handleResponse(res);
37
45
  const etag = res.headers.get('etag');
38
46
  const data = format === 'json' ? await res.json() : await res.text();
@@ -43,7 +51,7 @@ export async function putMap(baseUrl, id, body, { etag, contentType = 'text/yaml
43
51
  const headers = { 'Content-Type': contentType };
44
52
  if (etag) headers['If-Match'] = etag;
45
53
  if (lockPasswordHash) headers['X-Lock-Password'] = lockPasswordHash;
46
- const res = await fetch(`${baseUrl}/api/maps/${id}`, {
54
+ const res = await request(`${baseUrl}/api/maps/${id}`, {
47
55
  method: 'PUT',
48
56
  headers,
49
57
  body,
@@ -53,7 +61,7 @@ export async function putMap(baseUrl, id, body, { etag, contentType = 'text/yaml
53
61
  }
54
62
 
55
63
  export async function postMap(baseUrl, body, contentType = 'text/yaml') {
56
- const res = await fetch(`${baseUrl}/api/maps`, {
64
+ const res = await request(`${baseUrl}/api/maps`, {
57
65
  method: 'POST',
58
66
  headers: { 'Content-Type': contentType },
59
67
  body,
@@ -63,19 +71,19 @@ export async function postMap(baseUrl, body, contentType = 'text/yaml') {
63
71
  }
64
72
 
65
73
  export async function getNewId(baseUrl) {
66
- const res = await fetch(`${baseUrl}/api/maps/new-id`);
74
+ const res = await request(`${baseUrl}/api/maps/new-id`);
67
75
  await handleResponse(res);
68
76
  return res.json();
69
77
  }
70
78
 
71
79
  export async function getLockStatus(baseUrl, id) {
72
- const res = await fetch(`${baseUrl}/api/lock/${id}`);
80
+ const res = await request(`${baseUrl}/api/lock/${id}`);
73
81
  await handleResponse(res);
74
82
  return res.json();
75
83
  }
76
84
 
77
85
  export async function lockMap(baseUrl, id, passwordHash) {
78
- const res = await fetch(`${baseUrl}/api/lock/${id}`, {
86
+ const res = await request(`${baseUrl}/api/lock/${id}`, {
79
87
  method: 'POST',
80
88
  headers: { 'Content-Type': 'application/json' },
81
89
  body: JSON.stringify({ passwordHash }),
@@ -85,7 +93,7 @@ export async function lockMap(baseUrl, id, passwordHash) {
85
93
  }
86
94
 
87
95
  export async function unlockMap(baseUrl, id, passwordHash) {
88
- const res = await fetch(`${baseUrl}/api/lock/${id}/unlock`, {
96
+ const res = await request(`${baseUrl}/api/lock/${id}/unlock`, {
89
97
  method: 'POST',
90
98
  headers: { 'Content-Type': 'application/json' },
91
99
  body: JSON.stringify({ passwordHash }),
@@ -96,7 +104,7 @@ export async function unlockMap(baseUrl, id, passwordHash) {
96
104
  }
97
105
 
98
106
  export async function removeLock(baseUrl, id, passwordHash) {
99
- const res = await fetch(`${baseUrl}/api/lock/${id}/remove`, {
107
+ const res = await request(`${baseUrl}/api/lock/${id}/remove`, {
100
108
  method: 'POST',
101
109
  headers: { 'Content-Type': 'application/json' },
102
110
  body: JSON.stringify({ passwordHash }),
@@ -107,7 +115,7 @@ export async function removeLock(baseUrl, id, passwordHash) {
107
115
  }
108
116
 
109
117
  export async function getLog(baseUrl, id) {
110
- const res = await fetch(`${baseUrl}/api/maps/${id}/log`);
118
+ const res = await request(`${baseUrl}/api/maps/${id}/log`);
111
119
  await handleResponse(res);
112
120
  return res.json();
113
121
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "storymaps",
3
- "version": "1.2.7",
3
+ "version": "1.2.9",
4
4
  "description": "CLI tool for storymaps.io - import, export, validate, and manage user story maps from the terminal",
5
5
  "type": "module",
6
6
  "bin": {