intentdocs 0.10.0 → 0.11.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.
Files changed (30) hide show
  1. package/README.md +6 -1
  2. package/dist/chunk-64I7RLP3.js +171 -0
  3. package/dist/{chunk-IDNUBI2P.js → chunk-65EDM7QH.js} +6 -6
  4. package/dist/{chunk-NFIXRIHM.js → chunk-BNEU6TLJ.js} +55 -21
  5. package/dist/chunk-GFKZ5CRL.js +12 -0
  6. package/dist/{chunk-5USVG4DW.js → chunk-ID4B5RZ2.js} +42 -21
  7. package/dist/{chunk-EEKEF2FD.js → chunk-IORMKLAQ.js} +178 -121
  8. package/dist/{chunk-UF535HZL.js → chunk-K5SNF6CG.js} +13 -2
  9. package/dist/{chunk-HEHHSSUM.js → chunk-KPQ42YE6.js} +1 -1
  10. package/dist/{chunk-YUW3W43S.js → chunk-KTGQO3AE.js} +31 -8
  11. package/dist/{chunk-UR73WX5I.js → chunk-UQDMUKLP.js} +2 -2
  12. package/dist/{chunk-LXQXJULN.js → chunk-XZE2XP6O.js} +3 -3
  13. package/dist/ci-NOCV3XUV.js +11 -0
  14. package/dist/{complexity-ZOB3BPVD.js → complexity-OJVM37NF.js} +4 -4
  15. package/dist/{import-prd-MJUCIMHX.js → import-prd-5FCVXCYO.js} +14 -7
  16. package/dist/index.js +377 -359
  17. package/dist/{keys-5PA3D6CX.js → keys-2W5V56FS.js} +32 -11
  18. package/dist/{log-EVLWM64C.js → log-DFCCAAJN.js} +23 -7
  19. package/dist/{map-extract-W6WJLVOF.js → map-extract-YUCFLGJS.js} +2 -2
  20. package/dist/{map-import-LL4AIAQE.js → map-import-7RN2NEUZ.js} +7 -6
  21. package/dist/{map-schema-6GL2UKZQ.js → map-schema-5BEIJM2J.js} +1 -1
  22. package/dist/{projects-F7GMH66U.js → projects-AYAYVFKL.js} +2 -2
  23. package/dist/push-33V7ANJW.js +462 -0
  24. package/dist/{research-4ASCZSZN.js → research-SHO5SXVQ.js} +4 -4
  25. package/dist/scope-RRGJHS2G.js +13 -0
  26. package/dist/setup-UILQVYIB.js +14 -0
  27. package/package.json +6 -5
  28. package/dist/ci-LBOCMRKZ.js +0 -11
  29. package/dist/scope-S3LZZ3KB.js +0 -11
  30. package/dist/setup-YLUPBLK6.js +0 -14
package/README.md CHANGED
@@ -98,6 +98,11 @@ intent sync # Refresh INTENT.md
98
98
  intent sync --context # Regenerate CLAUDE.md from your project page
99
99
  ```
100
100
 
101
+ `intent` is a short alias for typing. Another tool on your machine may also be
102
+ called `intent`, so everything the CLI writes for your agent (CLAUDE.md, slash
103
+ commands, hints) spells commands as `npx intentdocs …`, which always runs this
104
+ package.
105
+
101
106
  ---
102
107
 
103
108
  ## All Commands
@@ -177,7 +182,7 @@ The intentdocs MCP server exposes **tools** (actions your agent can take) and **
177
182
 
178
183
  Give Claude Code or Cursor native tool and resource access to your story map.
179
184
 
180
- Add to `~/.claude/settings.json` (Claude Code) or your Cursor MCP config:
185
+ `intent setup` registers it for Claude Code in the project's `.mcp.json`. To add it yourself, run `claude mcp add --scope project intent -- npx intentdocs mcp` in the project, or add this to `.mcp.json` (Claude Code) or your Cursor MCP config:
181
186
 
182
187
  ```json
183
188
  {
@@ -0,0 +1,171 @@
1
+ import {
2
+ bindingLabel,
3
+ clearAuth,
4
+ getAuth,
5
+ saveAuth,
6
+ validateAuth
7
+ } from "./chunk-XZE2XP6O.js";
8
+ import {
9
+ API_BASE
10
+ } from "./chunk-UYTVTYNB.js";
11
+
12
+ // src/commands/login.ts
13
+ import { exec } from "child_process";
14
+ function finishLogin(data) {
15
+ saveAuth({
16
+ token: data.token,
17
+ apiBase: API_BASE,
18
+ ...data.userId && { userId: data.userId },
19
+ binding: data.binding ?? null,
20
+ ...data.posthogKey && { posthogKey: data.posthogKey },
21
+ ...data.posthogHost && { posthogHost: data.posthogHost }
22
+ });
23
+ console.log(`
24
+
25
+ \u2705 Logged in as ${data.userId ?? "unknown user"} \u2014 token ${bindingLabel(data.binding)}
26
+ `);
27
+ if (data.binding?.approvalStatus === "pending") {
28
+ console.log("An admin has been asked to approve it. Until they do, this token reads your personal projects only.\n");
29
+ }
30
+ console.log("You can now run: npx intentdocs init");
31
+ console.log(
32
+ "Optional: add your Anthropic API key to unlock AI features on any plan \u2014 npx intentdocs keys set"
33
+ );
34
+ }
35
+ function openBrowser(url) {
36
+ const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "${url}"` : `xdg-open "${url}"`;
37
+ exec(cmd);
38
+ }
39
+ function sleep(ms) {
40
+ return new Promise((r) => setTimeout(r, ms));
41
+ }
42
+ async function login() {
43
+ const existing = getAuth();
44
+ if (existing) {
45
+ const result = await validateAuth();
46
+ if (result.valid) {
47
+ console.log(`Already logged in (user: ${result.userId}). Run \`intentdocs logout\` to switch accounts.`);
48
+ return;
49
+ }
50
+ switch (result.reason) {
51
+ case "revoked":
52
+ console.log("Your stored token has been revoked. Starting a fresh login...\n");
53
+ break;
54
+ case "not_found":
55
+ console.log("Your stored token is no longer valid. Starting a fresh login...\n");
56
+ break;
57
+ case "network_error":
58
+ console.error("Could not reach intentdocs.com to verify your token. Check your internet connection and try again.");
59
+ process.exit(1);
60
+ break;
61
+ }
62
+ clearAuth();
63
+ }
64
+ console.log("Logging in to intentdocs...\n");
65
+ const res = await fetch(`${API_BASE}/api/cli/auth`, { method: "POST" });
66
+ if (!res.ok) {
67
+ console.error("Failed to start login flow. Try again.");
68
+ process.exit(1);
69
+ }
70
+ const { code } = await res.json();
71
+ const authUrl = `${API_BASE}/cli/auth?code=${code}`;
72
+ console.log("Opening browser to authorize...");
73
+ console.log(`If it doesn't open, visit: ${authUrl}
74
+ `);
75
+ openBrowser(authUrl);
76
+ const POLL_INTERVAL_MS = 3e3;
77
+ const INITIAL_TIMEOUT_S = 300;
78
+ const EXTEND_TIMEOUT_S = 120;
79
+ let totalWaitedS = 0;
80
+ let phase = "initial";
81
+ let timeoutS = INITIAL_TIMEOUT_S;
82
+ while (totalWaitedS < timeoutS) {
83
+ const elapsed = totalWaitedS;
84
+ const minutesLeft = Math.ceil((timeoutS - elapsed) / 60);
85
+ if (elapsed === 0) {
86
+ process.stdout.write("Waiting for browser approval...");
87
+ } else if (elapsed % 30 === 0) {
88
+ process.stdout.write(`
89
+ Still waiting for approval (${minutesLeft}m remaining)...`);
90
+ } else {
91
+ process.stdout.write(".");
92
+ }
93
+ await sleep(POLL_INTERVAL_MS);
94
+ totalWaitedS += POLL_INTERVAL_MS / 1e3;
95
+ try {
96
+ const checkRes = await fetch(`${API_BASE}/api/cli/auth?code=${code}`);
97
+ if (!checkRes.ok) continue;
98
+ const data = await checkRes.json();
99
+ if (data.status === "approved" && data.token) {
100
+ finishLogin(data);
101
+ return;
102
+ }
103
+ if (data.status === "expired") {
104
+ console.error("\n\nThe server-side login request expired. Run `npx intentdocs login` to start a new one.");
105
+ process.exit(1);
106
+ }
107
+ } catch {
108
+ }
109
+ }
110
+ if (phase === "initial") {
111
+ console.log("\n\nThe initial wait has ended. The browser approval hasn't come through yet.");
112
+ console.log("This can happen if you needed to create an account or got distracted.\n");
113
+ const rl = await import("readline");
114
+ const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
115
+ const answer = await new Promise(
116
+ (resolve) => iface.question("Keep waiting? (y/n): ", resolve)
117
+ );
118
+ iface.close();
119
+ if (answer.trim().toLowerCase().startsWith("y")) {
120
+ phase = "extended";
121
+ timeoutS = totalWaitedS + EXTEND_TIMEOUT_S;
122
+ console.log(`
123
+ Waiting ${EXTEND_TIMEOUT_S / 60} more minutes...`);
124
+ while (totalWaitedS < timeoutS) {
125
+ const minutesLeft = Math.ceil((timeoutS - totalWaitedS) / 60);
126
+ if (Math.floor(totalWaitedS) % 30 === 0 && totalWaitedS > INITIAL_TIMEOUT_S) {
127
+ process.stdout.write(`
128
+ Still waiting (${minutesLeft}m remaining)...`);
129
+ } else {
130
+ process.stdout.write(".");
131
+ }
132
+ await sleep(POLL_INTERVAL_MS);
133
+ totalWaitedS += POLL_INTERVAL_MS / 1e3;
134
+ try {
135
+ const checkRes = await fetch(`${API_BASE}/api/cli/auth?code=${code}`);
136
+ if (!checkRes.ok) continue;
137
+ const data = await checkRes.json();
138
+ if (data.status === "approved" && data.token) {
139
+ finishLogin(data);
140
+ return;
141
+ }
142
+ if (data.status === "expired") {
143
+ console.error("\n\nThe server-side login request expired. Run `npx intentdocs login` to start a new one.");
144
+ process.exit(1);
145
+ }
146
+ } catch {
147
+ }
148
+ }
149
+ }
150
+ }
151
+ console.error("\n\nLogin timed out. Run `npx intentdocs login` to try again.");
152
+ console.error("If the browser page is still open, complete it and re-run the command \u2014 your approval will still work within 10 minutes.\n");
153
+ process.exit(1);
154
+ }
155
+ async function logout() {
156
+ const { existsSync, unlinkSync } = await import("fs");
157
+ const { join } = await import("path");
158
+ const { homedir } = await import("os");
159
+ const authFile = join(homedir(), ".intent", "auth.json");
160
+ if (existsSync(authFile)) {
161
+ unlinkSync(authFile);
162
+ console.log("Logged out.");
163
+ } else {
164
+ console.log("Not logged in.");
165
+ }
166
+ }
167
+
168
+ export {
169
+ login,
170
+ logout
171
+ };
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  captureCli,
3
3
  shutdownCliAnalytics
4
- } from "./chunk-HEHHSSUM.js";
4
+ } from "./chunk-KPQ42YE6.js";
5
5
  import {
6
6
  requireConfig
7
- } from "./chunk-UF535HZL.js";
7
+ } from "./chunk-K5SNF6CG.js";
8
8
  import {
9
9
  closePrompt,
10
10
  confirm,
@@ -13,7 +13,7 @@ import {
13
13
  import {
14
14
  authenticatedFetch,
15
15
  requireValidAuth
16
- } from "./chunk-LXQXJULN.js";
16
+ } from "./chunk-XZE2XP6O.js";
17
17
  import {
18
18
  canonicalApiBase
19
19
  } from "./chunk-UYTVTYNB.js";
@@ -173,7 +173,7 @@ async function listKeys(projectId) {
173
173
  async function reportStatus(projectId) {
174
174
  const keys = (await listKeys(projectId)).filter((k) => !k.revoked_at);
175
175
  if (keys.length === 0) {
176
- console.log("No CI key yet. Run `intentdocs ci` to set up test reporting.");
176
+ console.log("No CI key yet. Run `npx intentdocs ci` to set up test reporting.");
177
177
  return;
178
178
  }
179
179
  const reported = keys.filter((k) => k.last_report_at);
@@ -198,7 +198,7 @@ async function ci(opts = {}) {
198
198
  await requireValidAuth();
199
199
  const projectId = config.projectId;
200
200
  if (!projectId) {
201
- console.error("This checkout has no project id. Run `intentdocs init` to reconnect.");
201
+ console.error("This checkout has no project id. Run `npx intentdocs init` to reconnect.");
202
202
  process.exit(1);
203
203
  }
204
204
  if (opts.status) {
@@ -278,7 +278,7 @@ Tell your agent to save each test with its file and full name so CI results can
278
278
  { project: projectId }
279
279
  );
280
280
  console.log(`
281
- After your next default-branch CI run, check how results matched with: intentdocs ci --status
281
+ After your next default-branch CI run, check how results matched with: npx intentdocs ci --status
282
282
  `);
283
283
  } finally {
284
284
  closePrompt();
@@ -79,9 +79,56 @@ var CONTEXT_DIR = ".intent/stories";
79
79
  function storyContextPath(storyId) {
80
80
  return join(process.cwd(), CONTEXT_DIR, `${storyId}.md`);
81
81
  }
82
+ var HAND_WRITTEN_SECTIONS = {
83
+ "Approach": "_Plan your approach here",
84
+ "Implementation notes": "_Notes added via",
85
+ "Decisions": "_Log decisions via",
86
+ "Relevant files": "_Add file paths here"
87
+ };
88
+ var GENERATED_HEADINGS = /* @__PURE__ */ new Set([
89
+ "User story",
90
+ "Done when",
91
+ "Design references",
92
+ "Spec",
93
+ "Implementation Plan",
94
+ ...Object.keys(HAND_WRITTEN_SECTIONS)
95
+ ]);
96
+ function readHandWrittenSections(content) {
97
+ const body = content.replace(/\n---\s*\n\s*Mark done:[^\n]*\s*$/, "\n");
98
+ const sections = /* @__PURE__ */ new Map();
99
+ let current = null;
100
+ let buffer = [];
101
+ const flush = () => {
102
+ if (current && current in HAND_WRITTEN_SECTIONS) {
103
+ const placeholder = HAND_WRITTEN_SECTIONS[current];
104
+ const text = buffer.filter((line) => !line.trim().startsWith(placeholder)).join("\n").trim();
105
+ if (text) sections.set(current, text);
106
+ }
107
+ };
108
+ for (const line of body.split("\n")) {
109
+ const heading = line.match(/^## (.+?)\s*$/);
110
+ if (heading && GENERATED_HEADINGS.has(heading[1])) {
111
+ flush();
112
+ current = heading[1];
113
+ buffer = [];
114
+ continue;
115
+ }
116
+ if (current) buffer.push(line);
117
+ }
118
+ flush();
119
+ return sections;
120
+ }
82
121
  function writeStoryContext(story) {
83
122
  const dir = join(process.cwd(), CONTEXT_DIR);
84
123
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
124
+ const path = storyContextPath(story.id);
125
+ const kept = existsSync(path) ? readHandWrittenSections(readFileSync(path, "utf-8")) : /* @__PURE__ */ new Map();
126
+ const section = (heading, placeholder) => [
127
+ `## ${heading}`,
128
+ ``,
129
+ kept.get(heading) ?? placeholder,
130
+ ``
131
+ ];
85
132
  const acLines = story.acceptance_criteria.length ? story.acceptance_criteria.flatMap((ac) => renderCriterionLines(ac)).join("\n") : "_No acceptance criteria defined._";
86
133
  const specSection = story.notes?.trim() ? [`## Spec`, ``, story.notes.trim(), ``] : [];
87
134
  const designRefs = story.design_refs ?? [];
@@ -96,13 +143,10 @@ function writeStoryContext(story) {
96
143
  plan.markdown,
97
144
  ``,
98
145
  ...plan.files_to_create?.length ? [`### Files to create`, ``, ...plan.files_to_create.map((f) => `- \`${f}\``), ``] : [],
99
- ...plan.files_to_modify?.length ? [`### Files to modify`, ``, ...plan.files_to_modify.map((f) => `- \`${f}\``), ``] : []
100
- ] : [
101
- `## Approach`,
102
- ``,
103
- `_Plan your approach here before writing code._`,
104
- ``
105
- ];
146
+ ...plan.files_to_modify?.length ? [`### Files to modify`, ``, ...plan.files_to_modify.map((f) => `- \`${f}\``), ``] : [],
147
+ // An approach written before the plan arrived is still the developer's.
148
+ ...kept.has("Approach") ? section("Approach", "") : []
149
+ ] : section("Approach", `_Plan your approach here before writing code._`);
106
150
  const md = [
107
151
  `# ${story.title}`,
108
152
  ``,
@@ -122,23 +166,13 @@ function writeStoryContext(story) {
122
166
  ...designRefsSection,
123
167
  ...specSection,
124
168
  ...approachSection,
125
- `## Implementation notes`,
126
- ``,
127
- `_Notes added via \`intentdocs note ${storyRef(story)} "..."\`_`,
128
- ``,
129
- `## Decisions`,
130
- ``,
131
- `_Log decisions via \`intentdocs decide "..." -s ${storyRef(story)}\`_`,
132
- ``,
133
- `## Relevant files`,
134
- ``,
135
- `_Add file paths here as you explore the codebase._`,
136
- ``,
169
+ ...section("Implementation notes", `_Notes added via \`npx intentdocs note ${storyRef(story)} "..."\`_`),
170
+ ...section("Decisions", `_Log decisions via \`npx intentdocs decide "..." -s ${storyRef(story)}\`_`),
171
+ ...section("Relevant files", `_Add file paths here as you explore the codebase._`),
137
172
  `---`,
138
173
  ``,
139
- `Mark done: \`intentdocs done ${storyRef(story)}\``
174
+ `Mark done: \`npx intentdocs done ${storyRef(story)}\``
140
175
  ].join("\n");
141
- const path = storyContextPath(story.id);
142
176
  writeFileSync(path, md);
143
177
  return path;
144
178
  }
@@ -0,0 +1,12 @@
1
+ // src/lib/notes.ts
2
+ function appendToNotes(existing, note) {
3
+ const before = existing?.trimEnd() ?? "";
4
+ const added = note.trim();
5
+ return before ? `${before}
6
+
7
+ ${added}` : added;
8
+ }
9
+
10
+ export {
11
+ appendToNotes
12
+ };
@@ -1,9 +1,11 @@
1
1
  import {
2
2
  getContext
3
- } from "./chunk-YUW3W43S.js";
3
+ } from "./chunk-KTGQO3AE.js";
4
4
  import {
5
+ checkoutConfig,
6
+ hasConfig,
5
7
  writeConfig
6
- } from "./chunk-UF535HZL.js";
8
+ } from "./chunk-K5SNF6CG.js";
7
9
  import {
8
10
  ask,
9
11
  closePrompt,
@@ -12,7 +14,7 @@ import {
12
14
  import {
13
15
  authenticatedFetch,
14
16
  requireValidAuth
15
- } from "./chunk-LXQXJULN.js";
17
+ } from "./chunk-XZE2XP6O.js";
16
18
  import {
17
19
  API_BASE
18
20
  } from "./chunk-UYTVTYNB.js";
@@ -20,8 +22,36 @@ import {
20
22
  // src/commands/scope.ts
21
23
  import { writeFileSync, existsSync } from "fs";
22
24
  import { join } from "path";
25
+ async function connectCheckout(apiBase, projectId) {
26
+ if (hasConfig()) {
27
+ console.log(" This checkout is already connected (.intent/config.json) \u2014 left as is.");
28
+ console.log(" Run `npx intentdocs sync` to refresh it, or remove .intent/config.json and run `npx intentdocs init` to connect it to this project.\n");
29
+ return null;
30
+ }
31
+ const mapsRes = await authenticatedFetch(`/api/cli/projects/${projectId}/maps`);
32
+ if (!mapsRes.ok) {
33
+ console.log(" \u26A0 Could not fetch the project's story map \u2014 run `npx intentdocs init` to connect.\n");
34
+ return null;
35
+ }
36
+ const { maps, appUrl } = await mapsRes.json();
37
+ const map = maps[0];
38
+ if (!map) {
39
+ console.log(" \u26A0 The project has no story map yet \u2014 run `npx intentdocs init` once it has one.\n");
40
+ return null;
41
+ }
42
+ let workspace = null;
43
+ const projectsRes = await authenticatedFetch("/api/cli/projects");
44
+ if (projectsRes.ok) {
45
+ const { projects } = await projectsRes.json();
46
+ workspace = projects.find((p) => p.id === projectId)?.workspace ?? null;
47
+ }
48
+ const config = checkoutConfig({ apiBase, project: { id: projectId, workspace }, map, appUrl });
49
+ writeConfig(config);
50
+ console.log(" \u2713 Connected\n");
51
+ return config;
52
+ }
23
53
  async function scope(projectId) {
24
- await requireValidAuth();
54
+ const auth = await requireValidAuth();
25
55
  if (!projectId) {
26
56
  projectId = await ask(" Project ID (from `intentdocs start`): ");
27
57
  }
@@ -112,18 +142,8 @@ async function scope(projectId) {
112
142
  console.log(" \u2713 Blueprint generated (couldn't parse summary)\n");
113
143
  }
114
144
  console.log("\n Connecting project to your codebase...");
115
- const mapsRes = await authenticatedFetch(
116
- `/api/storymapper/project/${projectId}`
117
- );
118
- let mapId = null;
119
- if (mapsRes.ok) {
120
- const mapData = await mapsRes.json();
121
- if (mapData.mapId) mapId = mapData.mapId;
122
- }
123
- if (mapId) {
124
- const config = { apiBase: API_BASE, projectId, mapId };
125
- writeConfig(config);
126
- console.log(" \u2713 Connected\n");
145
+ const config = await connectCheckout(auth.apiBase, projectId);
146
+ if (config) {
127
147
  try {
128
148
  const md = await getContext(config);
129
149
  writeFileSync(join(process.cwd(), "INTENT.md"), md);
@@ -176,7 +196,7 @@ async function scope(projectId) {
176
196
  " Configure your AI tool (Claude Code / Cursor) with intentdocs agent workflows?"
177
197
  );
178
198
  if (wantSetup) {
179
- const { setup } = await import("./setup-YLUPBLK6.js");
199
+ const { setup } = await import("./setup-UILQVYIB.js");
180
200
  await setup();
181
201
  return;
182
202
  }
@@ -184,10 +204,10 @@ async function scope(projectId) {
184
204
  \u2705 Your project is scoped and ready to build!
185
205
 
186
206
  Next steps:
187
- \u2022 intent setup \u2014 configure your AI tool
188
- \u2022 intent status \u2014 see your story map progress
189
- \u2022 intent next \u2014 see what to build first
190
- \u2022 intent done <id> \u2014 mark stories as you complete them
207
+ \u2022 npx intentdocs setup \u2014 configure your AI tool
208
+ \u2022 npx intentdocs status \u2014 see your story map progress
209
+ \u2022 npx intentdocs next \u2014 see what to build first
210
+ \u2022 npx intentdocs done <id> \u2014 mark stories as you complete them
191
211
 
192
212
  Your story map is also live at ${API_BASE}
193
213
  Seek advice from the community and our resources.
@@ -226,5 +246,6 @@ ${projectId}
226
246
  }
227
247
 
228
248
  export {
249
+ connectCheckout,
229
250
  scope
230
251
  };