specstocode 0.6.2 → 0.7.2
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 +25 -0
- package/dist/{chunk-ZLSV4CRF.js → chunk-DBZNNVVE.js} +10 -2
- package/dist/{chunk-T46QKLGM.js → chunk-J6CQ4DQD.js} +39 -48
- package/dist/{chunk-2FQUCTDE.js → chunk-LRLG4Q6Q.js} +2 -2
- package/dist/chunk-XFT36SZE.js +21 -0
- package/dist/{complexity-27JMHAD2.js → complexity-ZPBJG3HN.js} +10 -1
- package/dist/{import-prd-IOV3SEQV.js → import-prd-M2VNDEMJ.js} +10 -1
- package/dist/index.js +45 -13
- package/dist/keys-3F2YZCL2.js +90 -0
- package/dist/{log-4OBVVVVF.js → log-LOMAPAMN.js} +1 -1
- package/dist/{research-NRHBPB5Q.js → research-TVMC62XK.js} +10 -1
- package/dist/{scope-NBFWSZEP.js → scope-HWAU4XT2.js} +2 -2
- package/dist/{setup-ZJCK6JB4.js → setup-U6SABQNQ.js} +2 -2
- package/package.json +2 -3
- package/bin/productbuilders.js +0 -2
package/README.md
CHANGED
|
@@ -158,6 +158,31 @@ Control token usage with `--mode`:
|
|
|
158
158
|
|
|
159
159
|
---
|
|
160
160
|
|
|
161
|
+
## AI Features & Pricing
|
|
162
|
+
|
|
163
|
+
Most commands work on every plan. These three are AI-powered and gated:
|
|
164
|
+
|
|
165
|
+
| Command | What it does |
|
|
166
|
+
|---------|--------------|
|
|
167
|
+
| `stc complexity` | AI complexity scoring — 1-10 score, risks, suggested effort per story |
|
|
168
|
+
| `stc research` | AI research with your project context — findings, recommendations, next steps |
|
|
169
|
+
| `stc import` | Parse a PRD or spec document into structured user stories |
|
|
170
|
+
|
|
171
|
+
(The same applies to the matching MCP tools: `analyze_complexity`, `research`, `import_prd`.)
|
|
172
|
+
|
|
173
|
+
You can unlock them two ways:
|
|
174
|
+
|
|
175
|
+
1. **Upgrade your plan** — see [specstocode.com/pricing](https://specstocode.com/pricing)
|
|
176
|
+
2. **Bring your own Anthropic key** — works on any plan, AI commands run on your key:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
stc keys set # add your Anthropic API key (encrypted at rest)
|
|
180
|
+
stc keys status # check whether a key is configured
|
|
181
|
+
stc keys remove # fall back to your plan's platform limits
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
161
186
|
## Links
|
|
162
187
|
|
|
163
188
|
- [specstocode.com](https://specstocode.com) — web app
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
// src/lib/config.ts
|
|
2
2
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
3
|
-
import { join } from "path";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
4
|
var CONFIG_DIR = ".specstocode";
|
|
5
5
|
var CONFIG_FILE = "config.json";
|
|
6
6
|
function configPath() {
|
|
7
|
+
let dir = process.cwd();
|
|
8
|
+
while (true) {
|
|
9
|
+
const candidate = join(dir, CONFIG_DIR, CONFIG_FILE);
|
|
10
|
+
if (existsSync(candidate)) return candidate;
|
|
11
|
+
const parent = dirname(dir);
|
|
12
|
+
if (parent === dir) break;
|
|
13
|
+
dir = parent;
|
|
14
|
+
}
|
|
7
15
|
return join(process.cwd(), CONFIG_DIR, CONFIG_FILE);
|
|
8
16
|
}
|
|
9
17
|
function hasConfig() {
|
|
@@ -20,7 +28,7 @@ function readConfig() {
|
|
|
20
28
|
function writeConfig(config) {
|
|
21
29
|
const dir = join(process.cwd(), CONFIG_DIR);
|
|
22
30
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
23
|
-
writeFileSync(
|
|
31
|
+
writeFileSync(join(dir, CONFIG_FILE), JSON.stringify(config, null, 2) + "\n");
|
|
24
32
|
}
|
|
25
33
|
function requireConfig() {
|
|
26
34
|
const config = readConfig();
|
|
@@ -8,12 +8,12 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
hasConfig,
|
|
10
10
|
requireConfig
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-DBZNNVVE.js";
|
|
12
12
|
|
|
13
13
|
// src/commands/setup.ts
|
|
14
14
|
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
15
|
+
import { join, dirname } from "path";
|
|
16
|
+
import { homedir } from "os";
|
|
17
17
|
async function setup() {
|
|
18
18
|
requireAuth();
|
|
19
19
|
if (!hasConfig()) {
|
|
@@ -43,16 +43,16 @@ async function setup() {
|
|
|
43
43
|
\u2022 Architecture and conventions scaffolding
|
|
44
44
|
|
|
45
45
|
Slash commands (use inside Claude Code / Cursor, not the terminal):
|
|
46
|
-
/
|
|
47
|
-
/
|
|
48
|
-
/
|
|
49
|
-
/
|
|
50
|
-
/
|
|
51
|
-
/
|
|
52
|
-
/
|
|
53
|
-
/
|
|
54
|
-
/
|
|
55
|
-
/
|
|
46
|
+
/stc-propose Propose a new change \u2014 creates stories + local spec
|
|
47
|
+
/stc-next Pick up the next story
|
|
48
|
+
/stc-implement Full implementation playbook
|
|
49
|
+
/stc-review Review against acceptance criteria
|
|
50
|
+
/stc-document Document what was built
|
|
51
|
+
/stc-test Generate tests from ACs
|
|
52
|
+
/stc-status Show progress
|
|
53
|
+
/stc-done Mark a story as done
|
|
54
|
+
/stc-sync Refresh product context
|
|
55
|
+
/stc-context Load focused context for the current story
|
|
56
56
|
|
|
57
57
|
Terminal commands (use anywhere):
|
|
58
58
|
stc status Show story map progress
|
|
@@ -123,7 +123,7 @@ Everything you record flows back to the story map so the team has context:
|
|
|
123
123
|
const commandsDir = join(cwd, ".claude", "commands");
|
|
124
124
|
if (!existsSync(commandsDir)) mkdirSync(commandsDir, { recursive: true });
|
|
125
125
|
writeFileSync(
|
|
126
|
-
join(commandsDir, "
|
|
126
|
+
join(commandsDir, "stc-next.md"),
|
|
127
127
|
`# Pick up the next story
|
|
128
128
|
|
|
129
129
|
1. Run \`stc next\` to get the highest-priority story
|
|
@@ -135,7 +135,7 @@ Everything you record flows back to the story map so the team has context:
|
|
|
135
135
|
`
|
|
136
136
|
);
|
|
137
137
|
writeFileSync(
|
|
138
|
-
join(commandsDir, "
|
|
138
|
+
join(commandsDir, "stc-implement.md"),
|
|
139
139
|
`# Implement a story
|
|
140
140
|
|
|
141
141
|
## Process
|
|
@@ -167,7 +167,7 @@ Everything you record flows back to the story map so the team has context:
|
|
|
167
167
|
`
|
|
168
168
|
);
|
|
169
169
|
writeFileSync(
|
|
170
|
-
join(commandsDir, "
|
|
170
|
+
join(commandsDir, "stc-review.md"),
|
|
171
171
|
`# Review against acceptance criteria
|
|
172
172
|
|
|
173
173
|
1. Identify the story being worked on
|
|
@@ -179,7 +179,7 @@ Everything you record flows back to the story map so the team has context:
|
|
|
179
179
|
`
|
|
180
180
|
);
|
|
181
181
|
writeFileSync(
|
|
182
|
-
join(commandsDir, "
|
|
182
|
+
join(commandsDir, "stc-document.md"),
|
|
183
183
|
`# Document a feature
|
|
184
184
|
|
|
185
185
|
1. Check which story was just completed
|
|
@@ -198,7 +198,7 @@ Everything you record flows back to the story map so the team has context:
|
|
|
198
198
|
`
|
|
199
199
|
);
|
|
200
200
|
writeFileSync(
|
|
201
|
-
join(commandsDir, "
|
|
201
|
+
join(commandsDir, "stc-test.md"),
|
|
202
202
|
`# Write tests for a story
|
|
203
203
|
|
|
204
204
|
1. Get the story details: \`stc next\` or provided story ID
|
|
@@ -209,19 +209,19 @@ Everything you record flows back to the story map so the team has context:
|
|
|
209
209
|
`
|
|
210
210
|
);
|
|
211
211
|
writeFileSync(
|
|
212
|
-
join(commandsDir, "
|
|
212
|
+
join(commandsDir, "stc-status.md"),
|
|
213
213
|
`Run \`stc status\` and \`stc stories --filter todo\` to show current progress and remaining backlog. Summarise what's done and what's left.`
|
|
214
214
|
);
|
|
215
215
|
writeFileSync(
|
|
216
|
-
join(commandsDir, "
|
|
216
|
+
join(commandsDir, "stc-done.md"),
|
|
217
217
|
`Ask which story was just completed, then run \`stc done <id>\`. Also ask if there are any implementation notes to add: \`stc note <id> "..."\``
|
|
218
218
|
);
|
|
219
219
|
writeFileSync(
|
|
220
|
-
join(commandsDir, "
|
|
220
|
+
join(commandsDir, "stc-sync.md"),
|
|
221
221
|
`Run \`stc sync\` to refresh SPECSTOCODE.md with the latest story map. Then re-read SPECSTOCODE.md to update your context.`
|
|
222
222
|
);
|
|
223
223
|
writeFileSync(
|
|
224
|
-
join(commandsDir, "
|
|
224
|
+
join(commandsDir, "stc-propose.md"),
|
|
225
225
|
`# Propose a new change
|
|
226
226
|
|
|
227
227
|
Use this when you want to plan a new feature, improvement, or fix before writing code.
|
|
@@ -246,7 +246,7 @@ stories there AND saves a local spec for the AI to reference during implementati
|
|
|
246
246
|
`
|
|
247
247
|
);
|
|
248
248
|
writeFileSync(
|
|
249
|
-
join(commandsDir, "
|
|
249
|
+
join(commandsDir, "stc-context.md"),
|
|
250
250
|
`# Load current story context
|
|
251
251
|
|
|
252
252
|
Use this to focus on a single story without loading the full story map.
|
|
@@ -269,33 +269,24 @@ Use this to focus on a single story without loading the full story map.
|
|
|
269
269
|
The file is at \`.specstocode/stories/<full-story-id>.md\` \u2014 read it directly.
|
|
270
270
|
`
|
|
271
271
|
);
|
|
272
|
-
console.log(" \u2713 Created slash commands: /
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
} catch {
|
|
283
|
-
console.log(`
|
|
284
|
-
To enable MCP tools (recommended), add to your Claude Code MCP config:
|
|
285
|
-
|
|
286
|
-
{
|
|
287
|
-
"mcpServers": {
|
|
288
|
-
"specstocode": {
|
|
289
|
-
"command": "npx",
|
|
290
|
-
"args": ["specstocode", "mcp"],
|
|
291
|
-
"cwd": "${cwd}"
|
|
292
|
-
}
|
|
272
|
+
console.log(" \u2713 Created slash commands: /stc-propose, /stc-next, /stc-implement, /stc-review, /stc-document, /stc-test, /stc-status, /stc-done, /stc-sync, /stc-context");
|
|
273
|
+
registerMcpWithClaudeCode();
|
|
274
|
+
}
|
|
275
|
+
function registerMcpWithClaudeCode() {
|
|
276
|
+
const settingsPath = join(homedir(), ".claude", "settings.json");
|
|
277
|
+
let settings = {};
|
|
278
|
+
if (existsSync(settingsPath)) {
|
|
279
|
+
try {
|
|
280
|
+
settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
281
|
+
} catch {
|
|
293
282
|
}
|
|
294
283
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
284
|
+
const servers = settings.mcpServers ?? {};
|
|
285
|
+
servers.specstocode = { command: "npx", args: ["specstocode", "mcp"] };
|
|
286
|
+
settings.mcpServers = servers;
|
|
287
|
+
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
288
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
289
|
+
console.log(" \u2713 Registered MCP server in ~/.claude/settings.json \u2014 restart Claude Code to activate");
|
|
299
290
|
}
|
|
300
291
|
function setupCursor(syncToken) {
|
|
301
292
|
const cwd = process.cwd();
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "./chunk-WPVDURTJ.js";
|
|
13
13
|
import {
|
|
14
14
|
writeConfig
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-DBZNNVVE.js";
|
|
16
16
|
|
|
17
17
|
// src/commands/scope.ts
|
|
18
18
|
import { writeFileSync, existsSync } from "fs";
|
|
@@ -195,7 +195,7 @@ async function scope(projectId) {
|
|
|
195
195
|
" Configure your AI tool (Claude Code / Cursor) with specstocode agent workflows?"
|
|
196
196
|
);
|
|
197
197
|
if (wantSetup) {
|
|
198
|
-
const { setup } = await import("./setup-
|
|
198
|
+
const { setup } = await import("./setup-U6SABQNQ.js");
|
|
199
199
|
await setup();
|
|
200
200
|
return;
|
|
201
201
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/lib/upgrade.ts
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
function isPlanGated(status) {
|
|
4
|
+
return status === 402 || status === 403;
|
|
5
|
+
}
|
|
6
|
+
var UPGRADE_MESSAGE_PLAIN = "This feature requires a Pro plan or your own Anthropic key.\n Upgrade: https://specstocode.com/pricing\n Or add your key: stc keys set";
|
|
7
|
+
function printUpgradeMessage() {
|
|
8
|
+
console.log();
|
|
9
|
+
console.log(
|
|
10
|
+
` ${chalk.yellow("This feature requires a Pro plan or your own Anthropic key.")}`
|
|
11
|
+
);
|
|
12
|
+
console.log(` Upgrade: ${chalk.cyan.underline("https://specstocode.com/pricing")}`);
|
|
13
|
+
console.log(` Or add your key: ${chalk.cyan("stc keys set")}`);
|
|
14
|
+
console.log();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
isPlanGated,
|
|
19
|
+
UPGRADE_MESSAGE_PLAIN,
|
|
20
|
+
printUpgradeMessage
|
|
21
|
+
};
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
listStories
|
|
3
3
|
} from "./chunk-QKMZ2SBR.js";
|
|
4
|
+
import {
|
|
5
|
+
isPlanGated,
|
|
6
|
+
printUpgradeMessage
|
|
7
|
+
} from "./chunk-XFT36SZE.js";
|
|
4
8
|
import {
|
|
5
9
|
requireConfig
|
|
6
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-DBZNNVVE.js";
|
|
7
11
|
|
|
8
12
|
// src/commands/complexity.ts
|
|
9
13
|
import ora from "ora";
|
|
@@ -36,6 +40,11 @@ async function complexity(storyId) {
|
|
|
36
40
|
}
|
|
37
41
|
);
|
|
38
42
|
if (!res.ok) {
|
|
43
|
+
if (isPlanGated(res.status)) {
|
|
44
|
+
spinner.fail(" Complexity analysis is not available on your plan");
|
|
45
|
+
printUpgradeMessage();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
39
48
|
spinner.fail(" Complexity analysis failed");
|
|
40
49
|
return;
|
|
41
50
|
}
|
|
@@ -2,9 +2,13 @@ import {
|
|
|
2
2
|
closePrompt,
|
|
3
3
|
confirm
|
|
4
4
|
} from "./chunk-WPVDURTJ.js";
|
|
5
|
+
import {
|
|
6
|
+
isPlanGated,
|
|
7
|
+
printUpgradeMessage
|
|
8
|
+
} from "./chunk-XFT36SZE.js";
|
|
5
9
|
import {
|
|
6
10
|
requireConfig
|
|
7
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-DBZNNVVE.js";
|
|
8
12
|
|
|
9
13
|
// src/commands/import-prd.ts
|
|
10
14
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -47,6 +51,11 @@ async function importPrd(filePath) {
|
|
|
47
51
|
}
|
|
48
52
|
);
|
|
49
53
|
if (!res.ok) {
|
|
54
|
+
if (isPlanGated(res.status)) {
|
|
55
|
+
spinner.fail(" PRD import is not available on your plan");
|
|
56
|
+
printUpgradeMessage();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
50
59
|
spinner.fail(" Import failed");
|
|
51
60
|
const errText = await res.text();
|
|
52
61
|
console.error(` ${errText.slice(0, 200)}`);
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
setup
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-J6CQ4DQD.js";
|
|
5
5
|
import {
|
|
6
6
|
scope
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-LRLG4Q6Q.js";
|
|
8
8
|
import {
|
|
9
9
|
API_BASE,
|
|
10
10
|
getAuth,
|
|
@@ -27,12 +27,16 @@ import {
|
|
|
27
27
|
confirm,
|
|
28
28
|
pausePrompt
|
|
29
29
|
} from "./chunk-WPVDURTJ.js";
|
|
30
|
+
import {
|
|
31
|
+
UPGRADE_MESSAGE_PLAIN,
|
|
32
|
+
isPlanGated
|
|
33
|
+
} from "./chunk-XFT36SZE.js";
|
|
30
34
|
import {
|
|
31
35
|
hasConfig,
|
|
32
36
|
readConfig,
|
|
33
37
|
requireConfig,
|
|
34
38
|
writeConfig
|
|
35
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-DBZNNVVE.js";
|
|
36
40
|
|
|
37
41
|
// src/index.ts
|
|
38
42
|
import { Command } from "commander";
|
|
@@ -81,6 +85,9 @@ async function login() {
|
|
|
81
85
|
saveAuth({ token: data.token, apiBase: API_BASE });
|
|
82
86
|
console.log("\u2705 Logged in to specstocode!\n");
|
|
83
87
|
console.log("You can now run: stc init");
|
|
88
|
+
console.log(
|
|
89
|
+
"Optional: add your Anthropic API key to unlock AI features on any plan \u2014 stc keys set"
|
|
90
|
+
);
|
|
84
91
|
return;
|
|
85
92
|
}
|
|
86
93
|
if (data.status === "expired") {
|
|
@@ -210,7 +217,7 @@ Connecting to "${map.title}"...`);
|
|
|
210
217
|
}
|
|
211
218
|
async function initWithSyncToken(syncToken) {
|
|
212
219
|
if (!syncToken.startsWith("pb_sync_") && !syncToken.startsWith("sc_sync_")) {
|
|
213
|
-
console.error("Invalid token. It should start with
|
|
220
|
+
console.error("Invalid token. It should start with sc_sync_");
|
|
214
221
|
process.exit(1);
|
|
215
222
|
}
|
|
216
223
|
console.log("\nConnecting to specstocode...");
|
|
@@ -832,7 +839,7 @@ async function start() {
|
|
|
832
839
|
closePrompt();
|
|
833
840
|
return;
|
|
834
841
|
}
|
|
835
|
-
const { scope: scope2 } = await import("./scope-
|
|
842
|
+
const { scope: scope2 } = await import("./scope-HWAU4XT2.js");
|
|
836
843
|
await scope2(projectId);
|
|
837
844
|
}
|
|
838
845
|
|
|
@@ -1618,7 +1625,12 @@ async function startMcpServer(mode = "all") {
|
|
|
1618
1625
|
body: JSON.stringify(body)
|
|
1619
1626
|
}
|
|
1620
1627
|
);
|
|
1621
|
-
if (!res.ok)
|
|
1628
|
+
if (!res.ok) {
|
|
1629
|
+
if (isPlanGated(res.status)) {
|
|
1630
|
+
return { content: [{ type: "text", text: UPGRADE_MESSAGE_PLAIN }] };
|
|
1631
|
+
}
|
|
1632
|
+
return { content: [{ type: "text", text: "Complexity analysis failed" }] };
|
|
1633
|
+
}
|
|
1622
1634
|
const result = await res.json();
|
|
1623
1635
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1624
1636
|
}
|
|
@@ -1633,7 +1645,12 @@ async function startMcpServer(mode = "all") {
|
|
|
1633
1645
|
body: JSON.stringify({ query })
|
|
1634
1646
|
}
|
|
1635
1647
|
);
|
|
1636
|
-
if (!res.ok)
|
|
1648
|
+
if (!res.ok) {
|
|
1649
|
+
if (isPlanGated(res.status)) {
|
|
1650
|
+
return { content: [{ type: "text", text: UPGRADE_MESSAGE_PLAIN }] };
|
|
1651
|
+
}
|
|
1652
|
+
return { content: [{ type: "text", text: "Research failed" }] };
|
|
1653
|
+
}
|
|
1637
1654
|
const result = await res.json();
|
|
1638
1655
|
return { content: [{ type: "text", text: result.research }] };
|
|
1639
1656
|
}
|
|
@@ -1648,7 +1665,12 @@ async function startMcpServer(mode = "all") {
|
|
|
1648
1665
|
body: JSON.stringify({ prd, format: args?.format ?? "PRD" })
|
|
1649
1666
|
}
|
|
1650
1667
|
);
|
|
1651
|
-
if (!res.ok)
|
|
1668
|
+
if (!res.ok) {
|
|
1669
|
+
if (isPlanGated(res.status)) {
|
|
1670
|
+
return { content: [{ type: "text", text: UPGRADE_MESSAGE_PLAIN }] };
|
|
1671
|
+
}
|
|
1672
|
+
return { content: [{ type: "text", text: "PRD import failed" }] };
|
|
1673
|
+
}
|
|
1652
1674
|
const result = await res.json();
|
|
1653
1675
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1654
1676
|
}
|
|
@@ -1706,6 +1728,16 @@ var program = new Command();
|
|
|
1706
1728
|
program.name("stc").description("Write specs, map stories, ship with AI \u2014 from your terminal").version(version);
|
|
1707
1729
|
program.command("login").description("Log in to specstocode (opens browser)").action(() => void login());
|
|
1708
1730
|
program.command("logout").description("Log out of specstocode").action(() => void logout());
|
|
1731
|
+
var keys = program.command("keys").description("Manage your Anthropic API key \u2014 unlock AI features on any plan");
|
|
1732
|
+
keys.command("set [key]").description("Save your Anthropic API key (prompts if omitted)").action((key) => {
|
|
1733
|
+
import("./keys-3F2YZCL2.js").then((m) => void m.keysSet(key));
|
|
1734
|
+
});
|
|
1735
|
+
keys.command("status").description("Show whether a key is set (masked)").action(() => {
|
|
1736
|
+
import("./keys-3F2YZCL2.js").then((m) => void m.keysStatus());
|
|
1737
|
+
});
|
|
1738
|
+
keys.command("remove").description("Remove your stored key").action(() => {
|
|
1739
|
+
import("./keys-3F2YZCL2.js").then((m) => void m.keysRemove());
|
|
1740
|
+
});
|
|
1709
1741
|
program.command("start").description("Start a new project \u2014 guided flow from problem to pitch").action(() => void start());
|
|
1710
1742
|
program.command("scope [projectId]").description("Scope a project \u2014 generate blueprint, story map, and mockup").action((id) => void scope(id));
|
|
1711
1743
|
program.command("setup").description("Configure your AI tool (Claude Code / Cursor) with specstocode agent workflows").action(() => void setup());
|
|
@@ -1744,19 +1776,19 @@ program.command("show <id>").description("Show the full spec for a story (accept
|
|
|
1744
1776
|
program.command("done <id>").description("Mark a story as done (accepts ID prefix)").action((id) => void done(id));
|
|
1745
1777
|
program.command("add <title>").description("Create a new story").option("-a, --activity <activity>", "Activity to add the story under").option("-p, --priority <priority>", "Priority: must, should, could", "should").option("-e, --effort <effort>", "Effort: S, M, L, XL", "M").action((title, opts) => void add(title, opts));
|
|
1746
1778
|
program.command("decide [title]").description("Log an architectural or product decision").option("-d, --description <desc>", "Decision description").option("-c, --category <cat>", "Category: architecture, product, technical, trade-off", "technical").option("-s, --story <id>", "Link to a story ID").action((title, opts) => {
|
|
1747
|
-
import("./log-
|
|
1779
|
+
import("./log-LOMAPAMN.js").then((m) => void m.logDecision(title, opts));
|
|
1748
1780
|
});
|
|
1749
1781
|
program.command("note <storyId> [note]").description("Add implementation notes to a story").action((storyId, note) => {
|
|
1750
|
-
import("./log-
|
|
1782
|
+
import("./log-LOMAPAMN.js").then((m) => void m.logNote(storyId, note));
|
|
1751
1783
|
});
|
|
1752
1784
|
program.command("complexity [storyId]").description("Analyze story complexity with AI \u2014 scores, risks, effort estimates").action((id) => {
|
|
1753
|
-
import("./complexity-
|
|
1785
|
+
import("./complexity-ZPBJG3HN.js").then((m) => void m.complexity(id));
|
|
1754
1786
|
});
|
|
1755
1787
|
program.command("research [query]").description("Research a topic with AI using your project context").action((query) => {
|
|
1756
|
-
import("./research-
|
|
1788
|
+
import("./research-TVMC62XK.js").then((m) => void m.research(query));
|
|
1757
1789
|
});
|
|
1758
1790
|
program.command("import [file]").description("Import a PRD or spec into your story map as stories").action((file) => {
|
|
1759
|
-
import("./import-prd-
|
|
1791
|
+
import("./import-prd-M2VNDEMJ.js").then((m) => void m.importPrd(file));
|
|
1760
1792
|
});
|
|
1761
1793
|
program.command("mcp").description("Start MCP server for Claude Code / Cursor integration").option("-m, --mode <mode>", "Tool mode: core, standard, all", "all").action((opts) => void startMcpServer(opts.mode));
|
|
1762
1794
|
program.command("openclaw-register").description("Register this project with OpenClaw MCP \u2014 gives spawned coding agents access to your story map").action(() => void openclawRegister());
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
requireAuth
|
|
3
|
+
} from "./chunk-WJOIFYIA.js";
|
|
4
|
+
import {
|
|
5
|
+
ask,
|
|
6
|
+
closePrompt
|
|
7
|
+
} from "./chunk-WPVDURTJ.js";
|
|
8
|
+
|
|
9
|
+
// src/commands/keys.ts
|
|
10
|
+
var KEYS_DOC_URL = "https://console.anthropic.com/settings/keys";
|
|
11
|
+
async function byokRequest(method, body) {
|
|
12
|
+
const auth = requireAuth();
|
|
13
|
+
const res = await fetch(`${auth.apiBase}/api/settings/byok`, {
|
|
14
|
+
method,
|
|
15
|
+
headers: {
|
|
16
|
+
Authorization: `Bearer ${auth.token}`,
|
|
17
|
+
...body ? { "Content-Type": "application/json" } : {}
|
|
18
|
+
},
|
|
19
|
+
...body ? { body: JSON.stringify(body) } : {}
|
|
20
|
+
});
|
|
21
|
+
const data = await res.json().catch(() => null);
|
|
22
|
+
return { ok: res.ok, status: res.status, data };
|
|
23
|
+
}
|
|
24
|
+
async function keysSet(key) {
|
|
25
|
+
let value = key?.trim();
|
|
26
|
+
if (!value) {
|
|
27
|
+
console.log("\n Optionally add your Anthropic API key to unlock AI features on any plan.");
|
|
28
|
+
console.log(" Your key is encrypted at rest and only used for your AI operations.");
|
|
29
|
+
console.log(` Get one at: ${KEYS_DOC_URL}
|
|
30
|
+
`);
|
|
31
|
+
value = (await ask(" Anthropic API key (sk-ant-...): ")).trim();
|
|
32
|
+
closePrompt();
|
|
33
|
+
}
|
|
34
|
+
if (!value) {
|
|
35
|
+
console.error(" No key provided. Run `stc keys set` to try again.");
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const { ok, data } = await byokRequest("POST", { key: value });
|
|
40
|
+
if (!ok) {
|
|
41
|
+
console.error(`
|
|
42
|
+
\u2717 ${data?.message ?? "Failed to save your key. Try again."}`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
console.log(`
|
|
46
|
+
\u2705 Key saved: ${data?.maskedKey}`);
|
|
47
|
+
console.log(" AI commands (complexity, research, import) now run on your key.\n");
|
|
48
|
+
} catch {
|
|
49
|
+
console.error("\n \u2717 Could not reach specstocode. Check your connection and try again.");
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function keysStatus() {
|
|
54
|
+
try {
|
|
55
|
+
const { ok, data } = await byokRequest("GET");
|
|
56
|
+
if (!ok) {
|
|
57
|
+
console.error(" \u2717 Failed to fetch key status.");
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
if (data?.enabled) {
|
|
61
|
+
console.log(`
|
|
62
|
+
Anthropic API key: ${data.maskedKey ?? "(stored but unreadable \u2014 re-run `stc keys set`)"}`);
|
|
63
|
+
console.log(" AI features run on your key.\n");
|
|
64
|
+
} else {
|
|
65
|
+
console.log("\n No Anthropic API key set.");
|
|
66
|
+
console.log(" Add one with `stc keys set` to unlock AI features on any plan.\n");
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
console.error(" \u2717 Could not reach specstocode. Check your connection and try again.");
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function keysRemove() {
|
|
74
|
+
try {
|
|
75
|
+
const { ok } = await byokRequest("DELETE");
|
|
76
|
+
if (!ok) {
|
|
77
|
+
console.error(" \u2717 Failed to remove your key.");
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
console.log("\n Key removed. AI features fall back to your plan's platform limits.\n");
|
|
81
|
+
} catch {
|
|
82
|
+
console.error(" \u2717 Could not reach specstocode. Check your connection and try again.");
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export {
|
|
87
|
+
keysRemove,
|
|
88
|
+
keysSet,
|
|
89
|
+
keysStatus
|
|
90
|
+
};
|
|
@@ -2,9 +2,13 @@ import {
|
|
|
2
2
|
ask,
|
|
3
3
|
closePrompt
|
|
4
4
|
} from "./chunk-WPVDURTJ.js";
|
|
5
|
+
import {
|
|
6
|
+
isPlanGated,
|
|
7
|
+
printUpgradeMessage
|
|
8
|
+
} from "./chunk-XFT36SZE.js";
|
|
5
9
|
import {
|
|
6
10
|
requireConfig
|
|
7
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-DBZNNVVE.js";
|
|
8
12
|
|
|
9
13
|
// src/commands/research.ts
|
|
10
14
|
import ora from "ora";
|
|
@@ -30,6 +34,11 @@ async function research(query) {
|
|
|
30
34
|
}
|
|
31
35
|
);
|
|
32
36
|
if (!res.ok) {
|
|
37
|
+
if (isPlanGated(res.status)) {
|
|
38
|
+
spinner.fail(" Research is not available on your plan");
|
|
39
|
+
printUpgradeMessage();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
33
42
|
spinner.fail(" Research failed");
|
|
34
43
|
return;
|
|
35
44
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
scope
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LRLG4Q6Q.js";
|
|
4
4
|
import "./chunk-WJOIFYIA.js";
|
|
5
5
|
import "./chunk-QKMZ2SBR.js";
|
|
6
6
|
import "./chunk-WPVDURTJ.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-DBZNNVVE.js";
|
|
8
8
|
export {
|
|
9
9
|
scope
|
|
10
10
|
};
|
package/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specstocode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "CLI for specstocode.com — connect your codebase to your product story map",
|
|
5
5
|
"bin": {
|
|
6
6
|
"specstocode": "bin/specstocode.js",
|
|
7
|
-
"productbuilders": "bin/productbuilders.js",
|
|
8
7
|
"stc": "bin/stc.js"
|
|
9
8
|
},
|
|
10
9
|
"scripts": {
|
|
11
|
-
"build": "tsup src/index.ts --format esm --clean && chmod +x dist/index.js && chmod +x bin/specstocode.js && chmod +x bin/
|
|
10
|
+
"build": "tsup src/index.ts --format esm --clean && chmod +x dist/index.js && chmod +x bin/specstocode.js && chmod +x bin/stc.js",
|
|
12
11
|
"dev": "tsup src/index.ts --format esm --watch",
|
|
13
12
|
"test": "vitest run",
|
|
14
13
|
"prepublishOnly": "npm run build"
|
package/bin/productbuilders.js
DELETED