intentdocs 0.10.1 → 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.
- package/README.md +1 -1
- package/dist/chunk-64I7RLP3.js +171 -0
- package/dist/{chunk-6X4BORMW.js → chunk-65EDM7QH.js} +1 -1
- package/dist/{chunk-F52HPVNQ.js → chunk-BNEU6TLJ.js} +54 -20
- package/dist/chunk-GFKZ5CRL.js +12 -0
- package/dist/{chunk-NHNBCNND.js → chunk-ID4B5RZ2.js} +37 -16
- package/dist/{chunk-PUGYRSMK.js → chunk-IORMKLAQ.js} +122 -65
- package/dist/{chunk-JX2RLGOM.js → chunk-K5SNF6CG.js} +11 -0
- package/dist/{chunk-34JCN66H.js → chunk-KTGQO3AE.js} +24 -1
- package/dist/{ci-XCOXFTLB.js → ci-NOCV3XUV.js} +2 -2
- package/dist/{complexity-X3IJKNNV.js → complexity-OJVM37NF.js} +2 -2
- package/dist/{import-prd-IJTM6FZE.js → import-prd-5FCVXCYO.js} +12 -5
- package/dist/index.js +344 -326
- package/dist/{keys-UVXCCWCS.js → keys-2W5V56FS.js} +30 -9
- package/dist/{log-XIH4BGFL.js → log-DFCCAAJN.js} +21 -5
- package/dist/{map-extract-V6D27I3H.js → map-extract-YUCFLGJS.js} +1 -1
- package/dist/{map-import-LORTI4K4.js → map-import-7RN2NEUZ.js} +5 -4
- package/dist/push-33V7ANJW.js +462 -0
- package/dist/{research-CO3W7PLC.js → research-SHO5SXVQ.js} +2 -2
- package/dist/scope-RRGJHS2G.js +13 -0
- package/dist/{setup-WSYIG2CR.js → setup-UILQVYIB.js} +5 -5
- package/package.json +6 -5
- package/dist/scope-ZZLKQEF5.js +0 -11
package/README.md
CHANGED
|
@@ -182,7 +182,7 @@ The intentdocs MCP server exposes **tools** (actions your agent can take) and **
|
|
|
182
182
|
|
|
183
183
|
Give Claude Code or Cursor native tool and resource access to your story map.
|
|
184
184
|
|
|
185
|
-
|
|
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:
|
|
186
186
|
|
|
187
187
|
```json
|
|
188
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
|
+
};
|
|
@@ -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
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
`
|
|
128
|
-
``,
|
|
129
|
-
`## Decisions`,
|
|
130
|
-
``,
|
|
131
|
-
`_Log decisions via \`npx 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
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
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getContext
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-KTGQO3AE.js";
|
|
4
4
|
import {
|
|
5
|
+
checkoutConfig,
|
|
6
|
+
hasConfig,
|
|
5
7
|
writeConfig
|
|
6
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-K5SNF6CG.js";
|
|
7
9
|
import {
|
|
8
10
|
ask,
|
|
9
11
|
closePrompt,
|
|
@@ -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
|
|
116
|
-
|
|
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-
|
|
199
|
+
const { setup } = await import("./setup-UILQVYIB.js");
|
|
180
200
|
await setup();
|
|
181
201
|
return;
|
|
182
202
|
}
|
|
@@ -226,5 +246,6 @@ ${projectId}
|
|
|
226
246
|
}
|
|
227
247
|
|
|
228
248
|
export {
|
|
249
|
+
connectCheckout,
|
|
229
250
|
scope
|
|
230
251
|
};
|