@codespring-app/cli 0.1.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 (2) hide show
  1. package/dist/index.js +1403 -0
  2. package/package.json +26 -0
package/dist/index.js ADDED
@@ -0,0 +1,1403 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+ var __create = Object.create;
4
+ var __getProtoOf = Object.getPrototypeOf;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __toESM = (mod, isNodeMode, target) => {
9
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
10
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
+ for (let key of __getOwnPropNames(mod))
12
+ if (!__hasOwnProp.call(to, key))
13
+ __defProp(to, key, {
14
+ get: () => mod[key],
15
+ enumerable: true
16
+ });
17
+ return to;
18
+ };
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, {
22
+ get: all[name],
23
+ enumerable: true,
24
+ configurable: true,
25
+ set: (newValue) => all[name] = () => newValue
26
+ });
27
+ };
28
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
29
+ var __require = import.meta.require;
30
+
31
+ // src/lib/credentials.ts
32
+ import { join } from "path";
33
+ import { homedir } from "os";
34
+ import { mkdir } from "fs/promises";
35
+ async function loadCredentials() {
36
+ try {
37
+ const file = Bun.file(CREDENTIALS_PATH);
38
+ if (!await file.exists())
39
+ return null;
40
+ return await file.json();
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+ async function saveCredentials(creds) {
46
+ await mkdir(CODESPRING_DIR, { recursive: true });
47
+ await Bun.write(CREDENTIALS_PATH, JSON.stringify(creds, null, 2));
48
+ }
49
+ async function clearCredentials() {
50
+ try {
51
+ const file = Bun.file(CREDENTIALS_PATH);
52
+ if (await file.exists()) {
53
+ await Bun.write(CREDENTIALS_PATH, "");
54
+ const { unlink } = await import("fs/promises");
55
+ await unlink(CREDENTIALS_PATH);
56
+ }
57
+ } catch {}
58
+ }
59
+ var CODESPRING_DIR, CREDENTIALS_PATH;
60
+ var init_credentials = __esm(() => {
61
+ CODESPRING_DIR = join(homedir(), ".codespring");
62
+ CREDENTIALS_PATH = join(CODESPRING_DIR, "credentials.json");
63
+ });
64
+
65
+ // src/lib/prompts.ts
66
+ import * as readline from "readline";
67
+ function isTTY() {
68
+ return process.stdin.isTTY === true;
69
+ }
70
+ function createRL() {
71
+ return readline.createInterface({
72
+ input: process.stdin,
73
+ output: process.stderr
74
+ });
75
+ }
76
+ async function selectOne(message, options) {
77
+ if (!isTTY()) {
78
+ throw new Error("Interactive prompts require a TTY. Use flags instead.");
79
+ }
80
+ const rl = createRL();
81
+ console.error(`
82
+ ${message}`);
83
+ for (let i = 0;i < options.length; i++) {
84
+ console.error(` ${i + 1}) ${options[i].label}`);
85
+ }
86
+ return new Promise((resolve, reject) => {
87
+ rl.question(`
88
+ Select (number): `, (answer) => {
89
+ rl.close();
90
+ const idx = parseInt(answer.trim(), 10) - 1;
91
+ if (idx >= 0 && idx < options.length) {
92
+ resolve(options[idx].value);
93
+ } else {
94
+ reject(new Error("Invalid selection"));
95
+ }
96
+ });
97
+ });
98
+ }
99
+ async function confirm(message) {
100
+ if (!isTTY())
101
+ return true;
102
+ const rl = createRL();
103
+ return new Promise((resolve) => {
104
+ rl.question(`${message} [Y/n] `, (answer) => {
105
+ rl.close();
106
+ const trimmed = answer.trim().toLowerCase();
107
+ resolve(trimmed === "" || trimmed === "y" || trimmed === "yes");
108
+ });
109
+ });
110
+ }
111
+ async function input(message) {
112
+ if (!isTTY()) {
113
+ throw new Error("Interactive prompts require a TTY. Use flags instead.");
114
+ }
115
+ const rl = createRL();
116
+ return new Promise((resolve) => {
117
+ rl.question(`${message} `, (answer) => {
118
+ rl.close();
119
+ resolve(answer.trim());
120
+ });
121
+ });
122
+ }
123
+ var init_prompts = () => {};
124
+
125
+ // src/lib/output.ts
126
+ function getMode(flags) {
127
+ if (flags.json)
128
+ return "json";
129
+ if (flags.md)
130
+ return "md";
131
+ return process.stdout.isTTY ? "md" : "json";
132
+ }
133
+ function output(data, flags = {}) {
134
+ const mode = getMode(flags);
135
+ if (mode === "md") {
136
+ console.log(toMarkdown(data));
137
+ } else {
138
+ console.log(JSON.stringify(data, null, flags.pretty ? 2 : undefined));
139
+ }
140
+ }
141
+ function success(message) {
142
+ console.error(`\u2713 ${message}`);
143
+ }
144
+ function toMarkdown(data, depth = 0) {
145
+ if (data == null)
146
+ return "_null_";
147
+ if (typeof data === "string")
148
+ return data;
149
+ if (typeof data === "number" || typeof data === "boolean")
150
+ return String(data);
151
+ if (Array.isArray(data)) {
152
+ if (data.length === 0)
153
+ return "_empty_";
154
+ if (typeof data[0] === "object" && data[0] !== null && !Array.isArray(data[0])) {
155
+ return toTable(data);
156
+ }
157
+ return data.map((item) => `- ${toMarkdown(item, depth + 1)}`).join(`
158
+ `);
159
+ }
160
+ if (typeof data === "object") {
161
+ const obj = data;
162
+ if ("data" in obj && Object.keys(obj).length <= 3) {
163
+ const inner = obj.data;
164
+ const extra = [];
165
+ if ("count" in obj)
166
+ extra.push(`**${obj.count}** results`);
167
+ if ("hasMore" in obj && obj.hasMore)
168
+ extra.push("_(has more)_");
169
+ const footer = extra.length ? `
170
+
171
+ ${extra.join(" | ")}` : "";
172
+ return toMarkdown(inner, depth) + footer;
173
+ }
174
+ if ("content" in obj && typeof obj.content === "string" && obj.content.length > 100) {
175
+ const meta = Object.entries(obj).filter(([k]) => k !== "content").map(([k, v]) => `**${formatKey(k)}:** ${truncate(String(v ?? ""), 80)}`).join(`
176
+ `);
177
+ return `${meta}
178
+
179
+ ---
180
+
181
+ ${obj.content}`;
182
+ }
183
+ return Object.entries(obj).filter(([, v]) => v != null).map(([k, v]) => {
184
+ if (typeof v === "object" && v !== null) {
185
+ return `### ${formatKey(k)}
186
+
187
+ ${toMarkdown(v, depth + 1)}`;
188
+ }
189
+ return `**${formatKey(k)}:** ${truncate(String(v), 120)}`;
190
+ }).join(`
191
+ `);
192
+ }
193
+ return String(data);
194
+ }
195
+ function toTable(rows) {
196
+ const allKeys = Object.keys(rows[0]);
197
+ const columns = allKeys.filter((k) => {
198
+ if (SKIP_FIELDS.has(k))
199
+ return false;
200
+ if (rows.every((r) => r[k] == null))
201
+ return false;
202
+ const sample = rows[0][k];
203
+ if (typeof sample === "string" && sample.length > 300)
204
+ return false;
205
+ if (typeof sample === "object" && sample !== null && !Array.isArray(sample))
206
+ return false;
207
+ return true;
208
+ });
209
+ const cols = columns.slice(0, 7);
210
+ const header = cols.map(formatKey);
211
+ const separator = cols.map(() => "---");
212
+ const body = rows.map((row) => cols.map((col) => {
213
+ const val = row[col];
214
+ if (val == null)
215
+ return "-";
216
+ if (Array.isArray(val))
217
+ return val.length ? val.join(", ") : "-";
218
+ return truncate(String(val), 50);
219
+ }));
220
+ const lines = [
221
+ `| ${header.join(" | ")} |`,
222
+ `| ${separator.join(" | ")} |`,
223
+ ...body.map((row) => `| ${row.join(" | ")} |`)
224
+ ];
225
+ return lines.join(`
226
+ `);
227
+ }
228
+ function formatKey(key) {
229
+ return key.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()).replace(/\bId\b/g, "ID").replace(/\bUrl\b/g, "URL").replace(/\bPrd\b/g, "PRD");
230
+ }
231
+ function truncate(str, max) {
232
+ if (str.length <= max)
233
+ return str;
234
+ return `${str.slice(0, max - 1)}\u2026`;
235
+ }
236
+ var SKIP_FIELDS;
237
+ var init_output = __esm(() => {
238
+ SKIP_FIELDS = new Set([
239
+ "flowJson",
240
+ "mindmapData",
241
+ "plan",
242
+ "toolPreferences",
243
+ "marketResearchId",
244
+ "currentCheckpointId",
245
+ "initialCheckpointId",
246
+ "selectedStarterKit",
247
+ "userId",
248
+ "mindmapId"
249
+ ]);
250
+ });
251
+
252
+ // src/lib/oauth.ts
253
+ function generateCodeVerifier() {
254
+ const array = new Uint8Array(64);
255
+ crypto.getRandomValues(array);
256
+ return base64url(array);
257
+ }
258
+ async function computeCodeChallenge(verifier) {
259
+ const encoder = new TextEncoder;
260
+ const data = encoder.encode(verifier);
261
+ const hash = await crypto.subtle.digest("SHA-256", data);
262
+ return base64url(new Uint8Array(hash));
263
+ }
264
+ function base64url(bytes) {
265
+ let binary = "";
266
+ for (const byte of bytes) {
267
+ binary += String.fromCharCode(byte);
268
+ }
269
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
270
+ }
271
+ function openBrowser(url) {
272
+ const platform = process.platform;
273
+ if (platform === "darwin") {
274
+ Bun.spawn(["open", url]);
275
+ } else if (platform === "linux") {
276
+ Bun.spawn(["xdg-open", url]);
277
+ } else if (platform === "win32") {
278
+ Bun.spawn(["cmd", "/c", "start", url]);
279
+ }
280
+ }
281
+ async function startCallbackServer(preferredPort, expectedState) {
282
+ const port = await findAvailablePort(preferredPort);
283
+ let resolveCallback;
284
+ let rejectCallback;
285
+ const promise = new Promise((resolve, reject) => {
286
+ resolveCallback = resolve;
287
+ rejectCallback = reject;
288
+ });
289
+ const server = Bun.serve({
290
+ port,
291
+ fetch(req) {
292
+ const url = new URL(req.url);
293
+ if (url.pathname === "/callback") {
294
+ const code = url.searchParams.get("code");
295
+ const state = url.searchParams.get("state");
296
+ const error = url.searchParams.get("error");
297
+ if (error) {
298
+ const desc = url.searchParams.get("error_description") || error;
299
+ rejectCallback(new Error(`OAuth error: ${desc}`));
300
+ return new Response("<html><body><h2>Login failed</h2><p>You can close this tab.</p></body></html>", { headers: { "Content-Type": "text/html" } });
301
+ }
302
+ if (!code || state !== expectedState) {
303
+ rejectCallback(new Error("Invalid callback: missing code or state mismatch"));
304
+ return new Response("Invalid callback", { status: 400 });
305
+ }
306
+ resolveCallback({ code, state });
307
+ return new Response("<html><body><h2>Login successful!</h2><p>You can close this tab and return to the terminal.</p></body></html>", { headers: { "Content-Type": "text/html" } });
308
+ }
309
+ return new Response("Not found", { status: 404 });
310
+ }
311
+ });
312
+ return {
313
+ promise,
314
+ stop: () => server.stop(),
315
+ port
316
+ };
317
+ }
318
+ async function findAvailablePort(start) {
319
+ for (let port = start;port < start + 10; port++) {
320
+ try {
321
+ const server = Bun.serve({ port, fetch: () => new Response("") });
322
+ server.stop();
323
+ return port;
324
+ } catch {
325
+ continue;
326
+ }
327
+ }
328
+ throw new Error(`No available port found (tried ${start}-${start + 9})`);
329
+ }
330
+
331
+ // src/commands/auth/login.ts
332
+ var exports_login = {};
333
+ __export(exports_login, {
334
+ authLogin: () => authLogin
335
+ });
336
+ function getApiUrl() {
337
+ return process.env.CODESPRING_API_URL || DEFAULT_API_URL;
338
+ }
339
+ async function authLogin(flags) {
340
+ const apiUrl = flags.url || getApiUrl();
341
+ if (flags["api-key"]) {
342
+ await apiKeyLogin(apiUrl);
343
+ return;
344
+ }
345
+ await oauthLogin(apiUrl);
346
+ }
347
+ async function apiKeyLogin(apiUrl) {
348
+ const key = await input("Paste your API key (csk_...):");
349
+ if (!key.startsWith("csk_")) {
350
+ throw new Error("Invalid API key format. Keys start with csk_");
351
+ }
352
+ const response = await fetch(`${apiUrl}/api/workspaces`, {
353
+ headers: { "x-api-key": key }
354
+ });
355
+ if (!response.ok) {
356
+ throw new Error("Invalid API key. Please check and try again.");
357
+ }
358
+ await saveCredentials({
359
+ type: "api-key",
360
+ apiKey: key,
361
+ apiUrl
362
+ });
363
+ success("Authenticated with API key.");
364
+ }
365
+ async function oauthLogin(apiUrl) {
366
+ const existing = await loadCredentials();
367
+ if (existing?.accessToken) {
368
+ console.error("Already logged in. Use 'codespring auth logout' first, or --api-key to switch.");
369
+ }
370
+ const codeVerifier = generateCodeVerifier();
371
+ const codeChallenge = await computeCodeChallenge(codeVerifier);
372
+ const state = crypto.randomUUID();
373
+ const { promise: callbackPromise, stop, port } = await startCallbackServer(CALLBACK_PORT, state);
374
+ const redirectUri = `http://localhost:${port}/callback`;
375
+ const params = new URLSearchParams({
376
+ response_type: "code",
377
+ client_id: "codespring-cli",
378
+ redirect_uri: redirectUri,
379
+ scope: "openid profile email offline_access",
380
+ state,
381
+ code_challenge: codeChallenge,
382
+ code_challenge_method: "S256"
383
+ });
384
+ const authUrl = `${apiUrl}/api/auth/oauth2/authorize?${params}`;
385
+ console.error("Opening browser to log in...");
386
+ openBrowser(authUrl);
387
+ console.error("Waiting for authentication...");
388
+ try {
389
+ const { code } = await callbackPromise;
390
+ const tokenResponse = await fetch(`${apiUrl}/api/auth/oauth2/token`, {
391
+ method: "POST",
392
+ headers: {
393
+ "Content-Type": "application/x-www-form-urlencoded"
394
+ },
395
+ body: new URLSearchParams({
396
+ grant_type: "authorization_code",
397
+ code,
398
+ redirect_uri: redirectUri,
399
+ client_id: "codespring-cli",
400
+ code_verifier: codeVerifier
401
+ })
402
+ });
403
+ if (!tokenResponse.ok) {
404
+ const errText = await tokenResponse.text();
405
+ throw new Error(`Token exchange failed: ${errText}`);
406
+ }
407
+ const tokens = await tokenResponse.json();
408
+ await saveCredentials({
409
+ type: "oauth",
410
+ accessToken: tokens.access_token,
411
+ refreshToken: tokens.refresh_token,
412
+ expiresAt: new Date(Date.now() + tokens.expires_in * 1000).toISOString(),
413
+ apiUrl
414
+ });
415
+ success("Logged in successfully.");
416
+ } finally {
417
+ stop();
418
+ }
419
+ }
420
+ var DEFAULT_API_URL = "https://server.codespring.app", CALLBACK_PORT = 8923;
421
+ var init_login = __esm(() => {
422
+ init_credentials();
423
+ init_prompts();
424
+ init_output();
425
+ });
426
+
427
+ // src/commands/auth/status.ts
428
+ var exports_status = {};
429
+ __export(exports_status, {
430
+ authStatus: () => authStatus
431
+ });
432
+ async function authStatus(flags) {
433
+ const creds = await loadCredentials();
434
+ if (!creds || !creds.accessToken && !creds.apiKey) {
435
+ output({ authenticated: false, message: "Not logged in" }, flags);
436
+ return;
437
+ }
438
+ output({
439
+ authenticated: true,
440
+ type: creds.type,
441
+ apiUrl: creds.apiUrl,
442
+ ...creds.expiresAt && { expiresAt: creds.expiresAt }
443
+ }, flags);
444
+ }
445
+ var init_status = __esm(() => {
446
+ init_credentials();
447
+ init_output();
448
+ });
449
+
450
+ // src/commands/auth/logout.ts
451
+ var exports_logout = {};
452
+ __export(exports_logout, {
453
+ authLogout: () => authLogout
454
+ });
455
+ async function authLogout() {
456
+ await clearCredentials();
457
+ success("Logged out.");
458
+ }
459
+ var init_logout = __esm(() => {
460
+ init_credentials();
461
+ init_output();
462
+ });
463
+
464
+ // src/lib/config.ts
465
+ import { join as join2 } from "path";
466
+ import { mkdir as mkdir2 } from "fs/promises";
467
+ function getConfigDir(cwd) {
468
+ return join2(cwd || process.cwd(), ".codespring");
469
+ }
470
+ function getConfigPath(cwd) {
471
+ return join2(getConfigDir(cwd), "config.json");
472
+ }
473
+ async function loadConfig(cwd) {
474
+ try {
475
+ const file = Bun.file(getConfigPath(cwd));
476
+ if (!await file.exists())
477
+ return null;
478
+ return await file.json();
479
+ } catch {
480
+ return null;
481
+ }
482
+ }
483
+ async function saveConfig(config, cwd) {
484
+ const dir = getConfigDir(cwd);
485
+ await mkdir2(dir, { recursive: true });
486
+ await Bun.write(getConfigPath(cwd), JSON.stringify(config, null, 2) + `
487
+ `);
488
+ await ensureGitignore(cwd);
489
+ }
490
+ async function ensureGitignore(cwd) {
491
+ const gitignorePath = join2(cwd || process.cwd(), ".gitignore");
492
+ try {
493
+ const file = Bun.file(gitignorePath);
494
+ if (await file.exists()) {
495
+ const content = await file.text();
496
+ if (!content.includes(".codespring")) {
497
+ await Bun.write(gitignorePath, content.trimEnd() + `
498
+
499
+ # CodeSpring local config
500
+ .codespring/
501
+ `);
502
+ }
503
+ }
504
+ } catch {}
505
+ }
506
+ var init_config = () => {};
507
+
508
+ // src/lib/api.ts
509
+ async function refreshToken(creds) {
510
+ if (!creds.refreshToken) {
511
+ throw new Error("No refresh token available. Run: codespring auth login");
512
+ }
513
+ const response = await fetch(`${creds.apiUrl}/api/auth/oauth2/token`, {
514
+ method: "POST",
515
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
516
+ body: new URLSearchParams({
517
+ grant_type: "refresh_token",
518
+ refresh_token: creds.refreshToken,
519
+ client_id: "codespring-cli"
520
+ })
521
+ });
522
+ if (!response.ok) {
523
+ throw new Error("Token refresh failed. Run: codespring auth login");
524
+ }
525
+ const tokens = await response.json();
526
+ const updated = {
527
+ ...creds,
528
+ accessToken: tokens.access_token,
529
+ refreshToken: tokens.refresh_token || creds.refreshToken,
530
+ expiresAt: new Date(Date.now() + tokens.expires_in * 1000).toISOString()
531
+ };
532
+ await saveCredentials(updated);
533
+ return updated;
534
+ }
535
+ async function apiCall(options) {
536
+ let creds = await loadCredentials();
537
+ if (!creds) {
538
+ throw new Error("Not authenticated. Run: codespring auth login");
539
+ }
540
+ if (creds.type === "oauth" && creds.expiresAt && new Date(creds.expiresAt) < new Date) {
541
+ creds = await refreshToken(creds);
542
+ }
543
+ const headers = {
544
+ "Content-Type": "application/json"
545
+ };
546
+ if (creds.type === "api-key" && creds.apiKey) {
547
+ headers["x-api-key"] = creds.apiKey;
548
+ } else if (creds.type === "oauth" && creds.accessToken) {
549
+ headers["Authorization"] = `Bearer ${creds.accessToken}`;
550
+ }
551
+ const apiUrl = creds.apiUrl || DEFAULT_API_URL2;
552
+ const baseUrl = apiUrl.replace(/\/api\/?$/, "");
553
+ const response = await fetch(`${baseUrl}/api${options.path}`, {
554
+ method: options.method,
555
+ headers,
556
+ body: options.body ? JSON.stringify(options.body) : undefined
557
+ });
558
+ const text = await response.text();
559
+ if (!response.ok) {
560
+ throw new Error(`API error (${response.status}): ${text.slice(0, 200)}`);
561
+ }
562
+ try {
563
+ return { data: JSON.parse(text), status: response.status };
564
+ } catch {
565
+ return { data: text, status: response.status };
566
+ }
567
+ }
568
+ var DEFAULT_API_URL2 = "https://server.codespring.app";
569
+ var init_api = __esm(() => {
570
+ init_credentials();
571
+ });
572
+
573
+ // src/commands/init.ts
574
+ var exports_init = {};
575
+ __export(exports_init, {
576
+ init: () => init
577
+ });
578
+ import { basename } from "path";
579
+ async function init(flags) {
580
+ const existing = await loadConfig();
581
+ if (existing && !flags.force) {
582
+ console.error(`Already linked to: ${existing.projectName}`);
583
+ const proceed2 = await confirm("Re-link to a different project?");
584
+ if (!proceed2)
585
+ return;
586
+ }
587
+ if (flags.project) {
588
+ const { data } = await apiCall({
589
+ path: `/projects/${flags.project}`,
590
+ method: "GET"
591
+ });
592
+ const project2 = data;
593
+ await saveConfig({
594
+ version: "1.0",
595
+ projectId: project2.id,
596
+ projectName: project2.name,
597
+ organizationId: project2.organizationId,
598
+ linkedAt: new Date().toISOString()
599
+ });
600
+ output({ linked: true, project: project2.name, projectId: project2.id }, flags);
601
+ return;
602
+ }
603
+ if (!process.stdin.isTTY) {
604
+ throw new Error("Interactive init requires a TTY. Use: codespring init --project <id>");
605
+ }
606
+ const proceed = await confirm(`Set up CodeSpring in "${process.cwd()}"?`);
607
+ if (!proceed)
608
+ return;
609
+ const { data: wsData } = await apiCall({
610
+ path: "/workspaces",
611
+ method: "GET"
612
+ });
613
+ const workspaces = wsData.data;
614
+ const workspace = await selectOne("Select a workspace:", workspaces.map((w) => ({
615
+ label: `${w.name} (${w.projectCount} projects)${w.role ? ` - ${w.role}` : ""}`,
616
+ value: w
617
+ })));
618
+ const orgParam = workspace.id ? `?organizationId=${workspace.id}` : "";
619
+ const { data: projData } = await apiCall({
620
+ path: `/projects${orgParam}`,
621
+ method: "GET"
622
+ });
623
+ const projects = projData.data;
624
+ if (projects.length === 0) {
625
+ throw new Error("No projects found in this workspace.");
626
+ }
627
+ const dirName = basename(process.cwd()).toLowerCase();
628
+ const autoMatch = projects.find((p) => p.name.toLowerCase() === dirName || slugify(p.name) === dirName);
629
+ let project;
630
+ if (autoMatch) {
631
+ const useMatch = await confirm(`Found project "${autoMatch.name}". Link to it?`);
632
+ if (useMatch) {
633
+ project = autoMatch;
634
+ } else {
635
+ project = await selectOne("Select a project:", projects.map((p) => ({ label: p.name, value: p })));
636
+ }
637
+ } else {
638
+ project = await selectOne("Select a project:", projects.map((p) => ({ label: p.name, value: p })));
639
+ }
640
+ await saveConfig({
641
+ version: "1.0",
642
+ projectId: project.id,
643
+ projectName: project.name,
644
+ organizationId: project.organizationId,
645
+ linkedAt: new Date().toISOString()
646
+ });
647
+ success(`Linked to ${project.name}`);
648
+ output({ linked: true, project: project.name, projectId: project.id }, flags);
649
+ }
650
+ function slugify(str) {
651
+ return str.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
652
+ }
653
+ var init_init = __esm(() => {
654
+ init_config();
655
+ init_api();
656
+ init_output();
657
+ init_prompts();
658
+ });
659
+
660
+ // src/commands/status.ts
661
+ var exports_status2 = {};
662
+ __export(exports_status2, {
663
+ status: () => status
664
+ });
665
+ async function status(flags) {
666
+ const config = await loadConfig();
667
+ const creds = await loadCredentials();
668
+ output({
669
+ linked: !!config,
670
+ ...config && {
671
+ projectId: config.projectId,
672
+ projectName: config.projectName,
673
+ organizationId: config.organizationId,
674
+ linkedAt: config.linkedAt
675
+ },
676
+ authenticated: !!(creds?.accessToken || creds?.apiKey),
677
+ authType: creds?.type || null
678
+ }, flags);
679
+ }
680
+ var init_status2 = __esm(() => {
681
+ init_config();
682
+ init_credentials();
683
+ init_output();
684
+ });
685
+
686
+ // src/commands/workspaces.ts
687
+ var exports_workspaces = {};
688
+ __export(exports_workspaces, {
689
+ workspaces: () => workspaces
690
+ });
691
+ async function workspaces(flags) {
692
+ const { data } = await apiCall({ path: "/workspaces", method: "GET" });
693
+ output(data, flags);
694
+ }
695
+ var init_workspaces = __esm(() => {
696
+ init_api();
697
+ init_output();
698
+ });
699
+
700
+ // src/commands/projects.ts
701
+ var exports_projects = {};
702
+ __export(exports_projects, {
703
+ projects: () => projects
704
+ });
705
+ async function projects(flags) {
706
+ const params = new URLSearchParams;
707
+ if (flags.org)
708
+ params.set("organizationId", flags.org);
709
+ const query = params.toString();
710
+ const { data } = await apiCall({
711
+ path: `/projects${query ? `?${query}` : ""}`,
712
+ method: "GET"
713
+ });
714
+ output(data, flags);
715
+ }
716
+ var init_projects = __esm(() => {
717
+ init_api();
718
+ init_output();
719
+ });
720
+
721
+ // src/lib/ensure-linked.ts
722
+ async function ensureLinked() {
723
+ const config = await loadConfig();
724
+ if (config)
725
+ return config;
726
+ if (!process.stdin.isTTY) {
727
+ throw new Error("Not linked to a CodeSpring project. Run: codespring init");
728
+ }
729
+ console.error("This directory is not linked to a CodeSpring project.");
730
+ await init({});
731
+ const newConfig = await loadConfig();
732
+ if (!newConfig) {
733
+ throw new Error("Failed to link project. Run: codespring init");
734
+ }
735
+ return newConfig;
736
+ }
737
+ var init_ensure_linked = __esm(() => {
738
+ init_config();
739
+ init_init();
740
+ });
741
+
742
+ // src/commands/features.ts
743
+ var exports_features = {};
744
+ __export(exports_features, {
745
+ features: () => features
746
+ });
747
+ async function features(flags) {
748
+ const config = await ensureLinked();
749
+ const { data } = await apiCall({
750
+ path: `/mindmap-features/project/${config.projectId}`,
751
+ method: "GET"
752
+ });
753
+ output(data, flags);
754
+ }
755
+ var init_features = __esm(() => {
756
+ init_api();
757
+ init_output();
758
+ init_ensure_linked();
759
+ });
760
+
761
+ // src/commands/tasks.ts
762
+ var exports_tasks = {};
763
+ __export(exports_tasks, {
764
+ tasks: () => tasks,
765
+ taskUpdate: () => taskUpdate,
766
+ taskStart: () => taskStart,
767
+ taskDone: () => taskDone
768
+ });
769
+ async function tasks(flags) {
770
+ const config = await ensureLinked();
771
+ const params = new URLSearchParams;
772
+ if (flags.status)
773
+ params.set("status", flags.status);
774
+ if (flags.feature)
775
+ params.set("featureId", flags.feature);
776
+ if (flags.priority)
777
+ params.set("priority", flags.priority);
778
+ const query = params.toString();
779
+ const { data } = await apiCall({
780
+ path: `/tasks/${config.projectId}${query ? `?${query}` : ""}`,
781
+ method: "GET"
782
+ });
783
+ output(data, flags);
784
+ }
785
+ async function taskStart(taskId, flags) {
786
+ const config = await ensureLinked();
787
+ const { data } = await apiCall({
788
+ path: `/tasks/${config.projectId}/${taskId}`,
789
+ method: "PATCH",
790
+ body: { status: "in_progress" }
791
+ });
792
+ output(data, flags);
793
+ }
794
+ async function taskDone(taskId, flags) {
795
+ const config = await ensureLinked();
796
+ const { data } = await apiCall({
797
+ path: `/tasks/${config.projectId}/${taskId}`,
798
+ method: "PATCH",
799
+ body: { status: "done" }
800
+ });
801
+ output(data, flags);
802
+ }
803
+ async function taskUpdate(taskId, flags) {
804
+ const config = await ensureLinked();
805
+ const body = {};
806
+ if (flags.status)
807
+ body.status = flags.status;
808
+ if (flags.title)
809
+ body.title = flags.title;
810
+ if (flags.description)
811
+ body.description = flags.description;
812
+ if (flags.priority)
813
+ body.priority = flags.priority;
814
+ if (flags.estimate)
815
+ body.estimate = flags.estimate;
816
+ if (Object.keys(body).length === 0) {
817
+ throw new Error("No fields to update. Use --status, --title, etc.");
818
+ }
819
+ const { data } = await apiCall({
820
+ path: `/tasks/${config.projectId}/${taskId}`,
821
+ method: "PATCH",
822
+ body
823
+ });
824
+ output(data, flags);
825
+ }
826
+ var init_tasks = __esm(() => {
827
+ init_api();
828
+ init_output();
829
+ init_ensure_linked();
830
+ });
831
+
832
+ // src/commands/prds.ts
833
+ var exports_prds = {};
834
+ __export(exports_prds, {
835
+ prds: () => prds,
836
+ prdSync: () => prdSync,
837
+ prdGet: () => prdGet
838
+ });
839
+ async function prds(flags) {
840
+ const config = await ensureLinked();
841
+ const { data } = await apiCall({
842
+ path: `/prds/project/${config.projectId}/structure?includeContent=false`,
843
+ method: "GET"
844
+ });
845
+ output(data, flags);
846
+ }
847
+ async function prdGet(prdId, flags) {
848
+ const { data } = await apiCall({
849
+ path: `/prds/${prdId}`,
850
+ method: "GET"
851
+ });
852
+ output(data, flags);
853
+ }
854
+ async function prdSync(prdId, flags) {
855
+ let content;
856
+ if (flags.file) {
857
+ const file = Bun.file(flags.file);
858
+ if (!await file.exists()) {
859
+ throw new Error(`File not found: ${flags.file}`);
860
+ }
861
+ content = await file.text();
862
+ } else if (flags.stdin) {
863
+ const chunks = [];
864
+ const reader = process.stdin;
865
+ for await (const chunk of reader) {
866
+ chunks.push(typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk));
867
+ }
868
+ content = chunks.join("");
869
+ } else {
870
+ throw new Error("Provide --file <path> or --stdin");
871
+ }
872
+ const body = { content };
873
+ if (flags.name)
874
+ body.name = flags.name;
875
+ const { data } = await apiCall({
876
+ path: `/prds/${prdId}`,
877
+ method: "PUT",
878
+ body
879
+ });
880
+ output(data, flags);
881
+ }
882
+ var init_prds = __esm(() => {
883
+ init_api();
884
+ init_output();
885
+ init_ensure_linked();
886
+ });
887
+
888
+ // src/commands/mindmap.ts
889
+ var exports_mindmap = {};
890
+ __export(exports_mindmap, {
891
+ mindmapTechStack: () => mindmapTechStack,
892
+ mindmapNote: () => mindmapNote,
893
+ mindmapGet: () => mindmapGet,
894
+ mindmapFeatures: () => mindmapFeatures
895
+ });
896
+ async function mindmapGet(flags) {
897
+ const config = await ensureLinked();
898
+ const { data } = await apiCall({
899
+ path: `/mindmaps/project/${config.projectId}`,
900
+ method: "GET"
901
+ });
902
+ output(data, flags);
903
+ }
904
+ async function mindmapTechStack(flags) {
905
+ const config = await ensureLinked();
906
+ if (!flags.add) {
907
+ throw new Error("Provide --add '<json array of items>'");
908
+ }
909
+ let items;
910
+ try {
911
+ items = JSON.parse(flags.add);
912
+ } catch {
913
+ throw new Error("Invalid JSON for --add. Expected array of {id, title, description}");
914
+ }
915
+ const { data } = await apiCall({
916
+ path: `/mindmaps/project/${config.projectId}/tech-stack`,
917
+ method: "PATCH",
918
+ body: {
919
+ items,
920
+ mode: flags.replace ? "replace" : "merge"
921
+ }
922
+ });
923
+ output(data, flags);
924
+ }
925
+ async function mindmapFeatures(flags) {
926
+ const config = await ensureLinked();
927
+ if (!flags.add) {
928
+ throw new Error("Provide --add '<json array of items>'");
929
+ }
930
+ let items;
931
+ try {
932
+ items = JSON.parse(flags.add);
933
+ } catch {
934
+ throw new Error("Invalid JSON for --add. Expected array of {title, description}");
935
+ }
936
+ const { data } = await apiCall({
937
+ path: `/mindmaps/project/${config.projectId}/features`,
938
+ method: "PATCH",
939
+ body: {
940
+ items,
941
+ mode: flags.replace ? "replace" : "append"
942
+ }
943
+ });
944
+ output(data, flags);
945
+ }
946
+ async function mindmapNote(featureId, flags) {
947
+ const config = await ensureLinked();
948
+ if (!flags.text) {
949
+ throw new Error("Provide --text '<note content>'");
950
+ }
951
+ const content = flags.text;
952
+ const title = flags.title || "Notes";
953
+ const { data: mindmapData } = await apiCall({
954
+ path: `/mindmaps/project/${config.projectId}`,
955
+ method: "GET"
956
+ });
957
+ const mindmap = mindmapData;
958
+ if (!mindmap.flowJson) {
959
+ throw new Error("Mindmap has no flowJson data");
960
+ }
961
+ const nodes = mindmap.flowJson.nodes;
962
+ const edges = mindmap.flowJson.edges;
963
+ const featuresNode = nodes.find((n) => n.id === "node-features");
964
+ if (!featuresNode?.data?.items) {
965
+ throw new Error("Features node not found or has no items");
966
+ }
967
+ const featureItems = featuresNode.data.items;
968
+ const feature = featureItems.find((item) => item.id === featureId);
969
+ if (!feature) {
970
+ const validIds = featureItems.map((i) => i.id).join(", ");
971
+ throw new Error(`Feature "${featureId}" not found. Valid IDs: ${validIds}`);
972
+ }
973
+ const bridgeNodeId = `bridge-feature-${featureId}`;
974
+ let bridgeNode = nodes.find((n) => n.id === bridgeNodeId);
975
+ const newNodes = [];
976
+ const newEdges = [];
977
+ if (!bridgeNode) {
978
+ const featuresX = featuresNode.position?.x || 1200;
979
+ const featuresY = featuresNode.position?.y || 200;
980
+ const featureIndex = featureItems.findIndex((i) => i.id === featureId);
981
+ const bridgeX = featuresX + 496;
982
+ const bridgeY = featuresY + 56 + featureIndex * 50;
983
+ bridgeNode = {
984
+ id: bridgeNodeId,
985
+ type: "bridge",
986
+ position: { x: bridgeX, y: bridgeY },
987
+ data: {
988
+ featureId,
989
+ count: 1,
990
+ isExpanded: true,
991
+ handlePosition: "left"
992
+ }
993
+ };
994
+ newNodes.push(bridgeNode);
995
+ newEdges.push({
996
+ id: `edge-features-bridge-${featureId}`,
997
+ source: "node-features",
998
+ target: bridgeNodeId,
999
+ sourceHandle: `node-features-source-${featureId}`,
1000
+ targetHandle: `${bridgeNodeId}-target-${featureId}`,
1001
+ type: "default",
1002
+ animated: true,
1003
+ className: "stroke-foreground",
1004
+ style: { strokeWidth: 2 }
1005
+ });
1006
+ }
1007
+ const notesNodeId = `notes-${featureId}`;
1008
+ let notesNode = nodes.find((n) => n.id === notesNodeId);
1009
+ if (notesNode) {
1010
+ notesNode.data = { ...notesNode.data, title, content };
1011
+ } else {
1012
+ const bridgeX = bridgeNode.position?.x || 1700;
1013
+ const bridgeY = bridgeNode.position?.y || 256;
1014
+ notesNode = {
1015
+ id: notesNodeId,
1016
+ type: "tertiary",
1017
+ position: { x: bridgeX + 200, y: bridgeY },
1018
+ data: {
1019
+ type: "notes",
1020
+ title,
1021
+ content,
1022
+ handlePosition: "left"
1023
+ }
1024
+ };
1025
+ newNodes.push(notesNode);
1026
+ newEdges.push({
1027
+ id: `edge-bridge-${featureId}-notes`,
1028
+ source: bridgeNodeId,
1029
+ target: notesNodeId,
1030
+ sourceHandle: `${bridgeNodeId}-source-notes`,
1031
+ targetHandle: `${notesNodeId}-target-notes`,
1032
+ type: "default",
1033
+ animated: true,
1034
+ className: "stroke-foreground",
1035
+ style: { strokeWidth: 2 }
1036
+ });
1037
+ if (bridgeNode.data) {
1038
+ const currentCount = typeof bridgeNode.data.count === "number" ? bridgeNode.data.count : 0;
1039
+ bridgeNode.data.count = currentCount + 1;
1040
+ }
1041
+ }
1042
+ const updatedNodes = [
1043
+ ...nodes.filter((n) => n.id !== notesNodeId && !newNodes.some((nn) => nn.id === n.id)),
1044
+ ...newNodes,
1045
+ ...notesNode && !newNodes.includes(notesNode) ? [notesNode] : []
1046
+ ];
1047
+ const updatedEdges = [...edges, ...newEdges];
1048
+ const { data } = await apiCall({
1049
+ path: `/mindmaps/project/${config.projectId}`,
1050
+ method: "PUT",
1051
+ body: { flowJson: { nodes: updatedNodes, edges: updatedEdges } }
1052
+ });
1053
+ output(data, flags);
1054
+ }
1055
+ var init_mindmap = __esm(() => {
1056
+ init_api();
1057
+ init_output();
1058
+ init_ensure_linked();
1059
+ });
1060
+
1061
+ // src/commands/schema.ts
1062
+ var exports_schema = {};
1063
+ __export(exports_schema, {
1064
+ schema: () => schema
1065
+ });
1066
+ async function schema(flags) {
1067
+ output(SCHEMA, flags);
1068
+ }
1069
+ var SCHEMA;
1070
+ var init_schema = __esm(() => {
1071
+ init_output();
1072
+ SCHEMA = {
1073
+ project: {
1074
+ id: "string (UUID)",
1075
+ name: "string",
1076
+ description: "string | null",
1077
+ userId: "string",
1078
+ organizationId: "string | null",
1079
+ planningComplete: "boolean",
1080
+ createdAt: "Date",
1081
+ updatedAt: "Date"
1082
+ },
1083
+ mindmap: {
1084
+ id: "string",
1085
+ projectId: "string",
1086
+ flowJson: "{ nodes: MindmapNode[], edges: MindmapEdge[] }"
1087
+ },
1088
+ techStackItem: {
1089
+ id: 'string (e.g., "tech-react")',
1090
+ title: 'string (e.g., "React")',
1091
+ description: 'string (category, e.g., "Frontend")'
1092
+ },
1093
+ featureItem: {
1094
+ id: "string (optional, auto-generated)",
1095
+ title: 'string (e.g., "Authentication")',
1096
+ description: "string (brief description)"
1097
+ },
1098
+ prd: {
1099
+ id: "string (UUID)",
1100
+ name: "string",
1101
+ projectId: "string",
1102
+ prdType: "frontend | backend | database",
1103
+ content: "string (Markdown)"
1104
+ },
1105
+ techCategories: [
1106
+ "Frontend",
1107
+ "Backend",
1108
+ "Database",
1109
+ "State",
1110
+ "Styling",
1111
+ "Testing",
1112
+ "DevTools",
1113
+ "Infrastructure",
1114
+ "Utilities"
1115
+ ]
1116
+ };
1117
+ });
1118
+
1119
+ // src/commands/node-types.ts
1120
+ var exports_node_types = {};
1121
+ __export(exports_node_types, {
1122
+ nodeTypes: () => nodeTypes
1123
+ });
1124
+ async function nodeTypes(flags) {
1125
+ output(NODE_TYPES, flags);
1126
+ }
1127
+ var NODE_TYPES;
1128
+ var init_node_types = __esm(() => {
1129
+ init_output();
1130
+ NODE_TYPES = {
1131
+ primary: {
1132
+ description: "Central project node",
1133
+ exampleId: "node-primary",
1134
+ dataFields: ["title", "description", "githubUrl"]
1135
+ },
1136
+ secondary: {
1137
+ description: "Major category nodes (features, tech-stack, competitors, audience)",
1138
+ exampleIds: [
1139
+ "node-features",
1140
+ "node-tech-stack",
1141
+ "node-competitors",
1142
+ "node-audience"
1143
+ ],
1144
+ dataFields: ["title", "subtitle", "handlePosition", "layout", "items"]
1145
+ },
1146
+ bridge: {
1147
+ description: "Connector nodes between features and detail nodes",
1148
+ idPattern: "bridge-feature-{featureId}",
1149
+ dataFields: ["featureId", "count", "isExpanded", "handlePosition"]
1150
+ },
1151
+ tertiary: {
1152
+ description: "Detail nodes (notes, PRDs)",
1153
+ idPattern: "notes-{featureId} or prd-{featureId}",
1154
+ dataFields: ["type", "title", "content", "handlePosition"]
1155
+ },
1156
+ specialNodeIds: {
1157
+ "node-primary": "Main project node (required)",
1158
+ "node-features": "Features list (required)",
1159
+ "node-tech-stack": "Tech stack (required)",
1160
+ "node-competitors": "Competitors list",
1161
+ "node-audience": "Target audience"
1162
+ },
1163
+ handlePatterns: {
1164
+ featureToBridge: {
1165
+ sourceHandle: "node-features-source-{featureId}",
1166
+ targetHandle: "bridge-feature-{featureId}-target-{featureId}"
1167
+ },
1168
+ bridgeToNotes: {
1169
+ sourceHandle: "bridge-feature-{featureId}-source-notes",
1170
+ targetHandle: "notes-{featureId}-target-notes"
1171
+ }
1172
+ }
1173
+ };
1174
+ });
1175
+
1176
+ // src/index.ts
1177
+ var VERSION = "0.1.0";
1178
+ function parseArgs(argv) {
1179
+ const positional = [];
1180
+ const flags = {};
1181
+ for (let i = 0;i < argv.length; i++) {
1182
+ const arg = argv[i];
1183
+ if (arg.startsWith("--")) {
1184
+ const key = arg.slice(2);
1185
+ const next = argv[i + 1];
1186
+ if (next && !next.startsWith("--")) {
1187
+ flags[key] = next;
1188
+ i++;
1189
+ } else {
1190
+ flags[key] = true;
1191
+ }
1192
+ } else if (arg.startsWith("-") && arg.length === 2) {
1193
+ const key = arg.slice(1);
1194
+ const next = argv[i + 1];
1195
+ if (next && !next.startsWith("-")) {
1196
+ flags[key] = next;
1197
+ i++;
1198
+ } else {
1199
+ flags[key] = true;
1200
+ }
1201
+ } else {
1202
+ positional.push(arg);
1203
+ }
1204
+ }
1205
+ return { positional, flags };
1206
+ }
1207
+ function showHelp() {
1208
+ console.log(`codespring v${VERSION} \u2014 CodeSpring CLI
1209
+
1210
+ Usage: codespring <command> [options]
1211
+
1212
+ Auth:
1213
+ auth login [--api-key] [--url <api-url>] Log in via browser OAuth or API key
1214
+ auth status Show authentication status
1215
+ auth logout Clear stored credentials
1216
+
1217
+ Setup:
1218
+ init [--project <id>] [--force] Link directory to a CodeSpring project
1219
+ status Show linked project info
1220
+
1221
+ Data:
1222
+ workspaces List workspaces
1223
+ projects [--org <id>] List projects
1224
+ features List features for linked project
1225
+
1226
+ Tasks:
1227
+ tasks [--status <s>] [--feature <id>] [--priority <p>] List tasks
1228
+ task start <id> Mark task as in_progress
1229
+ task done <id> Mark task as done
1230
+ task update <id> --status <s> Update task fields
1231
+
1232
+ PRDs:
1233
+ prds List PRDs by feature
1234
+ prd <id> Get full PRD content
1235
+ prd sync <id> --file <path> Update PRD content from file
1236
+
1237
+ Mindmap:
1238
+ mindmap Get mindmap structure
1239
+ mindmap tech-stack --add '<json>' [--replace] Update tech stack
1240
+ mindmap features --add '<json>' [--replace] Update features
1241
+ mindmap note <featureId> --text '<text>' Add notes to a feature
1242
+
1243
+ Reference:
1244
+ schema Show data schema reference
1245
+ node-types Show mindmap node types
1246
+
1247
+ Options:
1248
+ --md Markdown output (default in terminal)
1249
+ --json JSON output (default when piped)
1250
+ --pretty Pretty-print JSON output
1251
+ --help, -h Show this help
1252
+ --version, -v Show version`);
1253
+ }
1254
+ async function main() {
1255
+ const { positional, flags } = parseArgs(process.argv.slice(2));
1256
+ const [command, subcommand, ...rest] = positional;
1257
+ if (flags.help || flags.h) {
1258
+ showHelp();
1259
+ return;
1260
+ }
1261
+ if (flags.version || flags.v) {
1262
+ console.log(VERSION);
1263
+ return;
1264
+ }
1265
+ if (!command) {
1266
+ showHelp();
1267
+ return;
1268
+ }
1269
+ try {
1270
+ switch (command) {
1271
+ case "auth": {
1272
+ switch (subcommand) {
1273
+ case "login": {
1274
+ const { authLogin: authLogin2 } = await Promise.resolve().then(() => (init_login(), exports_login));
1275
+ return authLogin2(flags);
1276
+ }
1277
+ case "status": {
1278
+ const { authStatus: authStatus2 } = await Promise.resolve().then(() => (init_status(), exports_status));
1279
+ return authStatus2(flags);
1280
+ }
1281
+ case "logout": {
1282
+ const { authLogout: authLogout2 } = await Promise.resolve().then(() => (init_logout(), exports_logout));
1283
+ return authLogout2();
1284
+ }
1285
+ default:
1286
+ console.error("Usage: codespring auth <login|status|logout>");
1287
+ process.exit(1);
1288
+ }
1289
+ break;
1290
+ }
1291
+ case "init": {
1292
+ const { init: init2 } = await Promise.resolve().then(() => (init_init(), exports_init));
1293
+ return init2(flags);
1294
+ }
1295
+ case "status": {
1296
+ const { status: status2 } = await Promise.resolve().then(() => (init_status2(), exports_status2));
1297
+ return status2(flags);
1298
+ }
1299
+ case "workspaces": {
1300
+ const { workspaces: workspaces2 } = await Promise.resolve().then(() => (init_workspaces(), exports_workspaces));
1301
+ return workspaces2(flags);
1302
+ }
1303
+ case "projects": {
1304
+ const { projects: projects2 } = await Promise.resolve().then(() => (init_projects(), exports_projects));
1305
+ return projects2(flags);
1306
+ }
1307
+ case "features": {
1308
+ const { features: features2 } = await Promise.resolve().then(() => (init_features(), exports_features));
1309
+ return features2(flags);
1310
+ }
1311
+ case "tasks": {
1312
+ const { tasks: tasks2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
1313
+ return tasks2(flags);
1314
+ }
1315
+ case "task": {
1316
+ switch (subcommand) {
1317
+ case "start": {
1318
+ const taskId = rest[0];
1319
+ if (!taskId)
1320
+ throw new Error("Usage: codespring task start <id>");
1321
+ const { taskStart: taskStart2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
1322
+ return taskStart2(taskId, flags);
1323
+ }
1324
+ case "done": {
1325
+ const taskId = rest[0];
1326
+ if (!taskId)
1327
+ throw new Error("Usage: codespring task done <id>");
1328
+ const { taskDone: taskDone2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
1329
+ return taskDone2(taskId, flags);
1330
+ }
1331
+ case "update": {
1332
+ const taskId = rest[0];
1333
+ if (!taskId)
1334
+ throw new Error("Usage: codespring task update <id> --status <s>");
1335
+ const { taskUpdate: taskUpdate2 } = await Promise.resolve().then(() => (init_tasks(), exports_tasks));
1336
+ return taskUpdate2(taskId, flags);
1337
+ }
1338
+ default:
1339
+ console.error("Usage: codespring task <start|done|update> <id>");
1340
+ process.exit(1);
1341
+ }
1342
+ break;
1343
+ }
1344
+ case "prds": {
1345
+ const { prds: prds2 } = await Promise.resolve().then(() => (init_prds(), exports_prds));
1346
+ return prds2(flags);
1347
+ }
1348
+ case "prd": {
1349
+ if (subcommand === "sync") {
1350
+ const prdId = rest[0];
1351
+ if (!prdId)
1352
+ throw new Error("Usage: codespring prd sync <id> --file <path>");
1353
+ const { prdSync: prdSync2 } = await Promise.resolve().then(() => (init_prds(), exports_prds));
1354
+ return prdSync2(prdId, flags);
1355
+ }
1356
+ if (!subcommand)
1357
+ throw new Error("Usage: codespring prd <id>");
1358
+ const { prdGet: prdGet2 } = await Promise.resolve().then(() => (init_prds(), exports_prds));
1359
+ return prdGet2(subcommand, flags);
1360
+ }
1361
+ case "mindmap": {
1362
+ switch (subcommand) {
1363
+ case "tech-stack": {
1364
+ const { mindmapTechStack: mindmapTechStack2 } = await Promise.resolve().then(() => (init_mindmap(), exports_mindmap));
1365
+ return mindmapTechStack2(flags);
1366
+ }
1367
+ case "features": {
1368
+ const { mindmapFeatures: mindmapFeatures2 } = await Promise.resolve().then(() => (init_mindmap(), exports_mindmap));
1369
+ return mindmapFeatures2(flags);
1370
+ }
1371
+ case "note": {
1372
+ const featureId = rest[0];
1373
+ if (!featureId)
1374
+ throw new Error("Usage: codespring mindmap note <featureId> --text '<text>'");
1375
+ const { mindmapNote: mindmapNote2 } = await Promise.resolve().then(() => (init_mindmap(), exports_mindmap));
1376
+ return mindmapNote2(featureId, flags);
1377
+ }
1378
+ default: {
1379
+ const { mindmapGet: mindmapGet2 } = await Promise.resolve().then(() => (init_mindmap(), exports_mindmap));
1380
+ return mindmapGet2(flags);
1381
+ }
1382
+ }
1383
+ }
1384
+ case "schema": {
1385
+ const { schema: schema2 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
1386
+ return schema2(flags);
1387
+ }
1388
+ case "node-types": {
1389
+ const { nodeTypes: nodeTypes2 } = await Promise.resolve().then(() => (init_node_types(), exports_node_types));
1390
+ return nodeTypes2(flags);
1391
+ }
1392
+ default:
1393
+ console.error(`Unknown command: ${command}`);
1394
+ showHelp();
1395
+ process.exit(1);
1396
+ }
1397
+ } catch (err) {
1398
+ const message = err instanceof Error ? err.message : String(err);
1399
+ console.error(JSON.stringify({ error: message }));
1400
+ process.exit(1);
1401
+ }
1402
+ }
1403
+ main();
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@codespring-app/cli",
3
+ "version": "0.1.0",
4
+ "description": "CodeSpring CLI — project planning and management from the terminal",
5
+ "type": "module",
6
+ "bin": {
7
+ "codespring": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "dev": "bun src/index.ts",
14
+ "build": "bun build src/index.ts --outdir=dist --target=bun",
15
+ "compile": "bun build src/index.ts --compile --outfile=dist/codespring"
16
+ },
17
+ "dependencies": {
18
+ "zod": "^3.23.8"
19
+ },
20
+ "devDependencies": {
21
+ "@types/bun": "latest"
22
+ },
23
+ "peerDependencies": {
24
+ "typescript": "^5"
25
+ }
26
+ }