@neuralconfig/nrepo 0.0.4 → 0.0.6

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 +119 -75
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -96,6 +96,7 @@ var listIdeas = (c, params) => {
96
96
  var createIdea = (c, data) => request(c, "POST", "/ideas", data);
97
97
  var getIdea = (c, id) => request(c, "GET", `/ideas/${id}`);
98
98
  var updateIdea = (c, id, data) => request(c, "PATCH", `/ideas/${id}`, data);
99
+ var bulkUpdateIdeas = (c, data) => request(c, "PATCH", "/ideas/bulk", data);
99
100
  var searchIdeas = (c, query, limit) => {
100
101
  const sp = new URLSearchParams({ q: query });
101
102
  if (limit) sp.set("limit", String(limit));
@@ -115,6 +116,10 @@ var createRelation = (c, sourceId, targetId, relationType = "related", note, for
115
116
  ...note ? { note } : {}
116
117
  });
117
118
  };
119
+ var createBulkRelations = (c, links, force) => {
120
+ const qs = force ? "?force=true" : "";
121
+ return request(c, "POST", `/map/relations${qs}`, { links });
122
+ };
118
123
  var deleteRelation = (c, relationId) => request(c, "DELETE", `/map/relations/${relationId}`);
119
124
  var deleteIdea = (c, id) => request(c, "DELETE", `/ideas/${id}`);
120
125
  var dismissDuplicate = (c, dupId) => request(c, "POST", `/ideas/duplicates/${dupId}/dismiss`);
@@ -471,21 +476,34 @@ import ora4 from "ora";
471
476
  async function logCommand(opts) {
472
477
  const config = await getAuthenticatedConfig();
473
478
  const spinner = opts.json ? null : ora4("Loading ideas...").start();
474
- const data = await listIdeas(config, {
475
- limit: opts.limit ? parseInt(opts.limit, 10) : 20,
476
- status: opts.status,
477
- tag: opts.tag
478
- });
479
+ const explicitLimit = opts.limit ? parseInt(opts.limit, 10) : void 0;
480
+ const pageSize = 100;
481
+ const allIdeas = [];
482
+ let offset = 0;
483
+ let remaining = explicitLimit ?? Infinity;
484
+ while (remaining > 0) {
485
+ const fetchLimit = Math.min(pageSize, remaining);
486
+ const data = await listIdeas(config, {
487
+ limit: fetchLimit,
488
+ offset,
489
+ status: opts.status,
490
+ tag: opts.tag
491
+ });
492
+ allIdeas.push(...data.ideas);
493
+ offset += data.ideas.length;
494
+ remaining -= data.ideas.length;
495
+ if (data.ideas.length < fetchLimit || !data.has_more) break;
496
+ }
479
497
  spinner?.stop();
480
498
  if (opts.json) {
481
- console.log(JSON.stringify(data.ideas, null, 2));
499
+ console.log(JSON.stringify(allIdeas, null, 2));
482
500
  return;
483
501
  }
484
- if (data.ideas.length === 0) {
502
+ if (allIdeas.length === 0) {
485
503
  console.log(chalk6.dim("No ideas found."));
486
504
  return;
487
505
  }
488
- for (const idea of data.ideas) {
506
+ for (const idea of allIdeas) {
489
507
  console.log(formatIdeaRow(idea));
490
508
  }
491
509
  }
@@ -500,11 +518,23 @@ var statusStyle2 = {
500
518
  shipped: chalk7.green,
501
519
  shelved: chalk7.dim
502
520
  };
521
+ async function fetchAllIdeas(config) {
522
+ const all = [];
523
+ let offset = 0;
524
+ const pageSize = 100;
525
+ while (true) {
526
+ const data = await listIdeas(config, { limit: pageSize, offset });
527
+ all.push(...data.ideas);
528
+ if (data.ideas.length < pageSize || !data.has_more) break;
529
+ offset += data.ideas.length;
530
+ }
531
+ return all;
532
+ }
503
533
  async function statusCommand(opts) {
504
534
  const config = await getAuthenticatedConfig();
505
535
  const spinner = opts.json ? null : ora5("Loading dashboard...").start();
506
- const [ideasData, dupsData, user] = await Promise.all([
507
- listIdeas(config, { limit: 100 }),
536
+ const [allIdeas, dupsData, user] = await Promise.all([
537
+ fetchAllIdeas(config),
508
538
  listDuplicates(config),
509
539
  getMe(config)
510
540
  ]);
@@ -512,8 +542,8 @@ async function statusCommand(opts) {
512
542
  if (opts.json) {
513
543
  console.log(JSON.stringify({
514
544
  user: { email: user.email, plan: user.plan },
515
- counts: formatStatusCounts(ideasData.ideas),
516
- total: ideasData.ideas.length,
545
+ counts: formatStatusCounts(allIdeas),
546
+ total: allIdeas.length,
517
547
  pending_duplicates: dupsData.duplicates.length
518
548
  }, null, 2));
519
549
  return;
@@ -521,16 +551,16 @@ async function statusCommand(opts) {
521
551
  console.log(chalk7.bold("NeuralRepo Dashboard"));
522
552
  console.log(chalk7.dim(`${user.display_name ?? user.email} \xB7 ${user.plan}
523
553
  `));
524
- const counts = formatStatusCounts(ideasData.ideas);
554
+ const counts = formatStatusCounts(allIdeas);
525
555
  console.log(chalk7.bold("Status breakdown"));
526
556
  for (const [status, count] of Object.entries(counts)) {
527
557
  const style = statusStyle2[status] ?? chalk7.white;
528
558
  const bar = "\u2588".repeat(Math.min(count, 40));
529
559
  console.log(` ${style(status.padEnd(10))} ${style(bar)} ${count}`);
530
560
  }
531
- console.log(` ${"total".padEnd(10)} ${chalk7.bold(String(ideasData.ideas.length))}
561
+ console.log(` ${"total".padEnd(10)} ${chalk7.bold(String(allIdeas.length))}
532
562
  `);
533
- const recent = ideasData.ideas.filter((i) => i.status === "captured").slice(0, 5);
563
+ const recent = allIdeas.filter((i) => i.status === "captured").slice(0, 5);
534
564
  if (recent.length > 0) {
535
565
  console.log(chalk7.bold("Recent captures"));
536
566
  for (const idea of recent) {
@@ -601,30 +631,20 @@ async function moveBulkCommand(status, opts) {
601
631
  process.exit(1);
602
632
  }
603
633
  const spinner = opts.json ? null : ora7(`Moving ${ids.length} ideas to ${status}...`).start();
604
- const results = await Promise.allSettled(
605
- ids.map(async (id) => {
606
- const idea = await updateIdea(config, id, { status });
607
- return { id, title: idea.title };
608
- })
609
- );
634
+ const result = await bulkUpdateIdeas(config, { ids, status });
610
635
  spinner?.stop();
611
636
  if (opts.json) {
612
- const output = results.map((r, i) => ({
613
- id: ids[i],
614
- success: r.status === "fulfilled",
615
- error: r.status === "rejected" ? r.reason.message : void 0
616
- }));
617
- console.log(JSON.stringify(output, null, 2));
637
+ console.log(JSON.stringify(result, null, 2));
618
638
  return;
619
639
  }
620
- console.log(`Moved ${ids.length} ideas to ${status}:`);
621
- results.forEach((r, i) => {
622
- if (r.status === "fulfilled") {
623
- console.log(` ${chalk8.green("\u2713")} #${ids[i]} ${r.value.title}`);
640
+ for (const r of result.results) {
641
+ if (r.status === "updated") {
642
+ console.log(` ${chalk8.green("\u2713")} #${r.id}`);
624
643
  } else {
625
- console.log(` ${chalk8.red("\u2717")} #${ids[i]} ${r.reason.message}`);
644
+ console.log(` ${chalk8.red("\u2717")} #${r.id} ${r.error}`);
626
645
  }
627
- });
646
+ }
647
+ console.log(`${chalk8.green(result.updated.toString())} moved to ${status}, ${result.errors > 0 ? chalk8.red(result.errors.toString()) : "0"} errors`);
628
648
  }
629
649
 
630
650
  // src/commands/tag.ts
@@ -660,32 +680,20 @@ async function tagAddCommand(tag, opts) {
660
680
  process.exit(1);
661
681
  }
662
682
  const spinner = opts.json ? null : ora8(`Adding tag "${tag}" to ${ids.length} ideas...`).start();
663
- const results = await Promise.allSettled(
664
- ids.map(async (id) => {
665
- const existing = await getIdea(config, id);
666
- const merged = [.../* @__PURE__ */ new Set([...existing.tags, tag])];
667
- await updateIdea(config, id, { tags: merged });
668
- return { id, title: existing.title };
669
- })
670
- );
683
+ const result = await bulkUpdateIdeas(config, { ids, add_tags: [tag] });
671
684
  spinner?.stop();
672
685
  if (opts.json) {
673
- const output = results.map((r, i) => ({
674
- id: ids[i],
675
- success: r.status === "fulfilled",
676
- error: r.status === "rejected" ? r.reason.message : void 0
677
- }));
678
- console.log(JSON.stringify(output, null, 2));
686
+ console.log(JSON.stringify(result, null, 2));
679
687
  return;
680
688
  }
681
- console.log(`Tagged ${ids.length} ideas with "${tag}":`);
682
- results.forEach((r, i) => {
683
- if (r.status === "fulfilled") {
684
- console.log(` ${chalk9.green("\u2713")} #${ids[i]} ${r.value.title}`);
689
+ for (const r of result.results) {
690
+ if (r.status === "updated") {
691
+ console.log(` ${chalk9.green("\u2713")} #${r.id}`);
685
692
  } else {
686
- console.log(` ${chalk9.red("\u2717")} #${ids[i]} ${r.reason.message}`);
693
+ console.log(` ${chalk9.red("\u2717")} #${r.id} ${r.error}`);
687
694
  }
688
- });
695
+ }
696
+ console.log(`${chalk9.green(result.updated.toString())} tagged with "${tag}", ${result.errors > 0 ? chalk9.red(result.errors.toString()) : "0"} errors`);
689
697
  }
690
698
  async function tagRemoveCommand(tag, opts) {
691
699
  const config = await getAuthenticatedConfig();
@@ -695,32 +703,20 @@ async function tagRemoveCommand(tag, opts) {
695
703
  process.exit(1);
696
704
  }
697
705
  const spinner = opts.json ? null : ora8(`Removing tag "${tag}" from ${ids.length} ideas...`).start();
698
- const results = await Promise.allSettled(
699
- ids.map(async (id) => {
700
- const existing = await getIdea(config, id);
701
- const filtered = existing.tags.filter((t) => t !== tag);
702
- await updateIdea(config, id, { tags: filtered });
703
- return { id, title: existing.title };
704
- })
705
- );
706
+ const result = await bulkUpdateIdeas(config, { ids, remove_tags: [tag] });
706
707
  spinner?.stop();
707
708
  if (opts.json) {
708
- const output = results.map((r, i) => ({
709
- id: ids[i],
710
- success: r.status === "fulfilled",
711
- error: r.status === "rejected" ? r.reason.message : void 0
712
- }));
713
- console.log(JSON.stringify(output, null, 2));
709
+ console.log(JSON.stringify(result, null, 2));
714
710
  return;
715
711
  }
716
- console.log(`Removed tag "${tag}" from ${ids.length} ideas:`);
717
- results.forEach((r, i) => {
718
- if (r.status === "fulfilled") {
719
- console.log(` ${chalk9.green("\u2713")} #${ids[i]} ${r.value.title}`);
712
+ for (const r of result.results) {
713
+ if (r.status === "updated") {
714
+ console.log(` ${chalk9.green("\u2713")} #${r.id}`);
720
715
  } else {
721
- console.log(` ${chalk9.red("\u2717")} #${ids[i]} ${r.reason.message}`);
716
+ console.log(` ${chalk9.red("\u2717")} #${r.id} ${r.error}`);
722
717
  }
723
- });
718
+ }
719
+ console.log(`${chalk9.green(result.updated.toString())} untagged "${tag}", ${result.errors > 0 ? chalk9.red(result.errors.toString()) : "0"} errors`);
724
720
  }
725
721
  function parseIds(idsStr) {
726
722
  return idsStr.split(",").map((s) => parseInt(s.trim(), 10)).filter((n) => !isNaN(n) && n > 0);
@@ -1111,6 +1107,9 @@ import chalk17 from "chalk";
1111
1107
  import ora16 from "ora";
1112
1108
  var VALID_TYPES = RELATION_TYPES.filter((t) => t !== "duplicate");
1113
1109
  async function linkCommand(sourceId, targetId, opts) {
1110
+ if (opts.batch) {
1111
+ return linkBatchCommand(opts);
1112
+ }
1114
1113
  const config = await getAuthenticatedConfig();
1115
1114
  const src = parseInt(sourceId, 10);
1116
1115
  const tgt = parseInt(targetId, 10);
@@ -1151,6 +1150,47 @@ async function linkCommand(sourceId, targetId, opts) {
1151
1150
  throw err;
1152
1151
  }
1153
1152
  }
1153
+ async function linkBatchCommand(opts) {
1154
+ const config = await getAuthenticatedConfig();
1155
+ const chunks = [];
1156
+ for await (const chunk of process.stdin) {
1157
+ chunks.push(chunk);
1158
+ }
1159
+ const input = Buffer.concat(chunks).toString("utf-8").trim();
1160
+ if (!input) {
1161
+ console.error("No input received. Pipe a JSON array to stdin.");
1162
+ console.error(chalk17.dim(` Example: echo '[{"source_idea_id":1,"target_idea_id":2}]' | nrepo link --batch`));
1163
+ process.exit(1);
1164
+ }
1165
+ let links;
1166
+ try {
1167
+ links = JSON.parse(input);
1168
+ if (!Array.isArray(links)) throw new Error("Input must be a JSON array");
1169
+ } catch (err) {
1170
+ console.error(`Invalid JSON: ${err.message}`);
1171
+ process.exit(1);
1172
+ }
1173
+ if (links.length === 0) {
1174
+ console.error("Empty array \u2014 nothing to link.");
1175
+ process.exit(1);
1176
+ }
1177
+ const spinner = opts.json ? null : ora16(`Creating ${links.length} links...`).start();
1178
+ const result = await createBulkRelations(config, links, opts.force);
1179
+ spinner?.stop();
1180
+ if (opts.json) {
1181
+ console.log(JSON.stringify(result, null, 2));
1182
+ return;
1183
+ }
1184
+ for (const r of result.results) {
1185
+ if (r.status === "created") {
1186
+ console.log(chalk17.green("\u2713") + ` Linked #${r.source_idea_id} \u2192 #${r.target_idea_id} (${r.relation_type})`);
1187
+ } else {
1188
+ console.log(chalk17.red("\u2717") + ` #${r.source_idea_id} \u2192 #${r.target_idea_id}: ${r.error}`);
1189
+ }
1190
+ }
1191
+ console.log("");
1192
+ console.log(`${chalk17.green(result.linked.toString())} created, ${result.errors > 0 ? chalk17.red(result.errors.toString()) : "0"} errors`);
1193
+ }
1154
1194
  async function unlinkCommand(sourceId, targetId, opts) {
1155
1195
  const config = await getAuthenticatedConfig();
1156
1196
  const src = parseInt(sourceId, 10);
@@ -1510,7 +1550,11 @@ async function updateSkillFile() {
1510
1550
  }
1511
1551
 
1512
1552
  // src/index.ts
1513
- var VERSION = "0.0.4";
1553
+ import { readFileSync as readFileSync2 } from "fs";
1554
+ import { fileURLToPath as fileURLToPath2 } from "url";
1555
+ import { dirname as dirname2, join as join4 } from "path";
1556
+ var __dirname = dirname2(fileURLToPath2(import.meta.url));
1557
+ var VERSION = JSON.parse(readFileSync2(join4(__dirname, "..", "package.json"), "utf8")).version;
1514
1558
  var program = new Command();
1515
1559
  program.name("nrepo").description("NeuralRepo \u2014 capture and manage ideas from the terminal").version(VERSION);
1516
1560
  program.command("login").description("Authenticate with NeuralRepo").option("--api-key", "Login with an API key instead of browser OAuth").action(wrap(loginCommand));
@@ -1546,7 +1590,7 @@ tagCmd.argument("[id]").argument("[tags...]").option("--json", "Output as JSON")
1546
1590
  program.command("pull <id>").description("Export idea + context as local files").option("--to <dir>", "Output directory (default: current)").option("--json", "Output as JSON (prints idea data instead of writing files)").option("--human", "Force human-readable output").action(wrap(pullCommand));
1547
1591
  program.command("diff <id> [id2]").description("Compare two ideas side-by-side (or diff against parent/related)").option("--json", "Output as JSON").option("--human", "Force human-readable output").action(wrap(diffCommand));
1548
1592
  program.command("branch <id>").description("Fork an idea into a new variant").option("--title <title>", "Override title for the branch").option("--body <body>", "Override body for the branch").option("--json", "Output as JSON").option("--human", "Force human-readable output").action(wrap(branchCommand));
1549
- program.command("link <source-id> <target-id>").description("Create a link between two ideas").option("--type <type>", "Link type (related|blocks|inspires|supersedes|parent)", "related").option("--note <note>", "Add a note to the link").option("--force", "Bypass cycle detection for soft-block types").option("--json", "Output as JSON").option("--human", "Force human-readable output").action(wrap(linkCommand));
1593
+ program.command("link [source-id] [target-id]").description("Create a link between two ideas. Use --batch to pipe a JSON array from stdin.").option("--type <type>", "Link type (related|blocks|inspires|supersedes|parent)", "related").option("--note <note>", "Add a note to the link").option("--force", "Bypass cycle detection for soft-block types").option("--batch", "Bulk mode: read JSON array of links from stdin").option("--json", "Output as JSON").option("--human", "Force human-readable output").action(wrap(linkCommand));
1550
1594
  program.command("unlink <source-id> <target-id>").description("Remove a link between two ideas").option("--json", "Output as JSON").option("--human", "Force human-readable output").action(wrap(unlinkCommand));
1551
1595
  program.command("links <id>").description("Show all links for an idea").option("--type <type>", "Filter by link type").option("--json", "Output as JSON").option("--human", "Force human-readable output").action(wrap(linksCommand));
1552
1596
  program.command("merge <keep-id> <absorb-id>").description("Merge two ideas (absorb the second into the first)").option("--force", "Skip confirmation").option("--json", "Output as JSON").option("--human", "Force human-readable output").action(wrap(mergeCommand));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neuralconfig/nrepo",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "NeuralRepo CLI — capture and manage ideas from the terminal",
5
5
  "type": "module",
6
6
  "bin": {