specstocode 0.6.1 → 0.7.1

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
@@ -8,6 +8,8 @@ Write structured specs, map your user stories, and let your AI coding agent do t
8
8
  npm install -g specstocode
9
9
  ```
10
10
 
11
+ Installs three aliases: **`stc`** (short, daily use), `specstocode`, and `productbuilders`.
12
+
11
13
  Or use without installing:
12
14
 
13
15
  ```bash
@@ -17,7 +19,7 @@ npx specstocode init
17
19
  ## How it works
18
20
 
19
21
  1. **Plan on the web** — create a project on [specstocode.com](https://specstocode.com), map your user stories, and write Gherkin specs for each one
20
- 2. **Connect your codebase** — run `specstocode init` in your project directory
22
+ 2. **Connect your codebase** — run `npx specstocode init` in your project directory
21
23
  3. **Build with AI** — your AI coding agent reads `SPECSTOCODE.md` for full product context: stories, acceptance criteria, decisions, and notes
22
24
 
23
25
  ```
@@ -40,7 +42,7 @@ specstocode.com Your project
40
42
  ### 1. Log in
41
43
 
42
44
  ```bash
43
- specstocode login
45
+ stc login
44
46
  ```
45
47
 
46
48
  Opens your browser to authenticate. One-time per machine.
@@ -50,7 +52,7 @@ Opens your browser to authenticate. One-time per machine.
50
52
  Run this inside your project directory after creating a project on [specstocode.com](https://specstocode.com):
51
53
 
52
54
  ```bash
53
- specstocode init
55
+ npx specstocode init
54
56
  ```
55
57
 
56
58
  This creates:
@@ -60,7 +62,7 @@ This creates:
60
62
  ### 3. Set up your AI tool
61
63
 
62
64
  ```bash
63
- specstocode setup
65
+ stc setup
64
66
  ```
65
67
 
66
68
  Configures [Claude Code](https://claude.com/claude-code) or [Cursor](https://cursor.com) with:
@@ -70,10 +72,10 @@ Configures [Claude Code](https://claude.com/claude-code) or [Cursor](https://cur
70
72
  ### 4. Build
71
73
 
72
74
  ```bash
73
- specstocode next # What to build next (highest priority story + acceptance criteria)
74
- specstocode done <id> # Mark a story complete
75
- specstocode stories # List all stories
76
- specstocode status # Progress dashboard
75
+ stc next # What to build next (highest priority story + acceptance criteria)
76
+ stc done <id> # Mark a story complete
77
+ stc stories # List all stories
78
+ stc status # Progress dashboard
77
79
  ```
78
80
 
79
81
  ---
@@ -84,29 +86,29 @@ specstocode status # Progress dashboard
84
86
 
85
87
  | Command | Description |
86
88
  |---------|-------------|
87
- | `specstocode login` | Authenticate (opens browser) |
88
- | `specstocode logout` | Log out |
89
- | `specstocode init` | Connect project directory to a story map |
90
- | `specstocode sync` | Refresh `SPECSTOCODE.md` with latest from the web |
91
- | `specstocode setup` | Configure Claude Code / Cursor |
89
+ | `stc login` | Authenticate (opens browser) |
90
+ | `stc logout` | Log out |
91
+ | `stc init` | Connect project directory to a story map |
92
+ | `stc sync` | Refresh `SPECSTOCODE.md` with latest from the web |
93
+ | `stc setup` | Configure Claude Code / Cursor |
92
94
 
93
95
  ### Story management
94
96
 
95
97
  | Command | Description |
96
98
  |---------|-------------|
97
- | `specstocode status` | Progress dashboard |
98
- | `specstocode stories [-f filter]` | List stories (filter: `todo` / `done` / keyword) |
99
- | `specstocode next` | Next priority story with acceptance criteria |
100
- | `specstocode done <id>` | Mark a story done (accepts ID prefix) |
101
- | `specstocode add <title>` | Create a new story |
102
- | `specstocode decide [title]` | Log an architectural decision |
103
- | `specstocode note <id> [text]` | Add implementation notes to a story |
99
+ | `stc status` | Progress dashboard |
100
+ | `stc stories [-f filter]` | List stories (filter: `todo` / `done` / keyword) |
101
+ | `stc next` | Next priority story with acceptance criteria |
102
+ | `stc done <id>` | Mark a story done (accepts ID prefix) |
103
+ | `stc add <title>` | Create a new story |
104
+ | `stc decide [title]` | Log an architectural decision |
105
+ | `stc note <id> [text]` | Add implementation notes to a story |
104
106
 
105
107
  ### MCP server
106
108
 
107
109
  | Command | Description |
108
110
  |---------|-------------|
109
- | `specstocode mcp [--mode]` | Start MCP server (`core` / `standard` / `all`) |
111
+ | `stc mcp [--mode]` | Start MCP server (`core` / `standard` / `all`) |
110
112
 
111
113
  ---
112
114
 
@@ -156,6 +158,31 @@ Control token usage with `--mode`:
156
158
 
157
159
  ---
158
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
+
159
186
  ## Links
160
187
 
161
188
  - [specstocode.com](https://specstocode.com) — web app
@@ -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,6 +1,10 @@
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
10
  } from "./chunk-ZLSV4CRF.js";
@@ -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,6 +2,10 @@ 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
11
  } from "./chunk-ZLSV4CRF.js";
@@ -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
@@ -27,6 +27,10 @@ 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,
@@ -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 pb_sync_");
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...");
@@ -1618,7 +1625,12 @@ async function startMcpServer(mode = "all") {
1618
1625
  body: JSON.stringify(body)
1619
1626
  }
1620
1627
  );
1621
- if (!res.ok) return { content: [{ type: "text", text: "Complexity analysis failed" }] };
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) return { content: [{ type: "text", text: "Research failed" }] };
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) return { content: [{ type: "text", text: "PRD import failed" }] };
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());
@@ -1750,13 +1782,13 @@ program.command("note <storyId> [note]").description("Add implementation notes t
1750
1782
  import("./log-4OBVVVVF.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-27JMHAD2.js").then((m) => void m.complexity(id));
1785
+ import("./complexity-MFKA2RFZ.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-NRHBPB5Q.js").then((m) => void m.research(query));
1788
+ import("./research-FKISZ323.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-IOV3SEQV.js").then((m) => void m.importPrd(file));
1791
+ import("./import-prd-SJWUNW5M.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,6 +2,10 @@ 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
11
  } from "./chunk-ZLSV4CRF.js";
@@ -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
  }
package/package.json CHANGED
@@ -1,14 +1,13 @@
1
1
  {
2
2
  "name": "specstocode",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
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/productbuilders.js && chmod +x bin/stc.js",
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"
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- import "../dist/index.js";