@sov3rain/nota 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,12 +29,15 @@ nota --no-open path/to/plan.md
29
29
  ### Set up agent instructions
30
30
 
31
31
  ```bash
32
- nota init # all agents (Claude, Codex, Cursor)
32
+ nota init # choose agents interactively
33
+ nota init all # all agents, non-interactive
33
34
  nota init claude # Claude only
34
35
  nota init codex # Codex only
35
36
  nota init cursor # Cursor only
36
37
  ```
37
38
 
39
+ The interactive mode starts with no agents selected, so you explicitly choose the agent instructions to install.
40
+
38
41
  Writes the annotation workflow instructions into `CLAUDE.md`, `AGENTS.md`, or `.cursor/rules/nota.mdc`. Safe to re-run — existing files are updated in place, not overwritten.
39
42
 
40
43
  ## How it works
@@ -0,0 +1,56 @@
1
+ export function getAgentInstructionBlock() {
2
+ return `<!-- nota:start -->
3
+ ## Plan Annotation Workflow
4
+
5
+ When the user asks for an implementation plan, create a Markdown file in:
6
+
7
+ \`\`\`text
8
+ .agent-plans/<linux-epoch-seconds>-<topic>.md
9
+ \`\`\`
10
+
11
+ The filename must start with the current Unix epoch timestamp in seconds, not a formatted date.
12
+
13
+ Then launch the local annotation tool and wait for the command to exit.
14
+
15
+ On Windows/PowerShell, use the npm \`.cmd\` shim explicitly:
16
+
17
+ \`\`\`powershell
18
+ nota.cmd .agent-plans/<file>.md
19
+ \`\`\`
20
+
21
+ Do not use \`Start-Process -FilePath nota\` on Windows/PowerShell; it can resolve to the extensionless npm shim and trigger the Windows "open with" dialog instead of running the CLI.
22
+
23
+ On macOS/Linux:
24
+
25
+ \`\`\`bash
26
+ nota .agent-plans/<file>.md
27
+ \`\`\`
28
+
29
+ Tell the user that the plan is open for annotation. Keep the command attached and do not continue with implementation until the \`nota\` process exits.
30
+
31
+ When the \`nota\` process exits, treat that as the signal that the user has finished annotating. Read the same Markdown file again. User feedback is stored as CriticMarkup:
32
+
33
+ \`\`\`md
34
+ {==text being discussed==}{>>user comment<<}
35
+ \`\`\`
36
+
37
+ Treat those comments as direct user feedback. Update the plan or implementation accordingly.
38
+
39
+ Once all annotations have been processed, remove every CriticMarkup annotation from the file, as well as the global annotations block if present:
40
+
41
+ \`\`\`md
42
+ <!-- nota:globals
43
+ [...]
44
+ -->
45
+ \`\`\`
46
+
47
+ Leave only the plain Markdown content.
48
+
49
+ If the \`nota\` command is unavailable, tell the user to install it globally:
50
+
51
+ \`\`\`bash
52
+ npm install -g nota
53
+ \`\`\`
54
+ <!-- nota:end -->
55
+ `;
56
+ }
@@ -0,0 +1,20 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getAgentInstructionBlock } from './agentInstructions.mjs';
3
+
4
+ describe('getAgentInstructionBlock', () => {
5
+ it('instructs agents to wait for the nota process to exit', () => {
6
+ const block = getAgentInstructionBlock();
7
+
8
+ expect(block).toContain('wait for the command to exit');
9
+ expect(block).toContain('do not continue with implementation until the `nota` process exits');
10
+ expect(block).toContain('When the `nota` process exits');
11
+ expect(block).not.toContain('until the user says they are done annotating');
12
+ });
13
+
14
+ it('keeps the Windows npm shim guidance', () => {
15
+ const block = getAgentInstructionBlock();
16
+
17
+ expect(block).toContain('nota.cmd .agent-plans/<file>.md');
18
+ expect(block).toContain('Do not use `Start-Process -FilePath nota`');
19
+ });
20
+ });
package/bin/nota.mjs CHANGED
@@ -7,6 +7,7 @@ import { access, mkdir, readFile, stat, writeFile } from 'node:fs/promises';
7
7
  import { dirname, extname, join, resolve } from 'node:path';
8
8
  import { fileURLToPath } from 'node:url';
9
9
  import { spawn } from 'node:child_process';
10
+ import { getAgentInstructionBlock } from './agentInstructions.mjs';
10
11
 
11
12
  const MAX_BODY_BYTES = 5 * 1024 * 1024;
12
13
  const MIME_TYPES = {
@@ -16,6 +17,12 @@ const MIME_TYPES = {
16
17
  '.json': 'application/json; charset=utf-8',
17
18
  '.svg': 'image/svg+xml',
18
19
  };
20
+ const AGENT_TARGETS = ['codex', 'claude', 'cursor'];
21
+ const AGENT_LABELS = {
22
+ codex: 'Codex',
23
+ claude: 'Claude',
24
+ cursor: 'Cursor',
25
+ };
19
26
 
20
27
  const args = process.argv.slice(2);
21
28
  const command = args[0];
@@ -29,7 +36,19 @@ if (command === 'init') {
29
36
  process.exit(0);
30
37
  }
31
38
 
32
- await initAgentInstructions(args[1] ?? 'all');
39
+ const targets = args[1] ? getAgentTargets(args[1]) : await promptForAgentTargets();
40
+
41
+ if (!targets) {
42
+ console.error('Unknown agent target. Expected codex, claude, cursor, or all.');
43
+ process.exit(1);
44
+ }
45
+
46
+ if (targets.length === 0) {
47
+ console.log('No agents selected. Nothing to install.');
48
+ process.exit(0);
49
+ }
50
+
51
+ await initAgentInstructions(targets);
33
52
  process.exit(0);
34
53
  }
35
54
 
@@ -262,17 +281,11 @@ function openBrowser(url) {
262
281
  function printUsage() {
263
282
  console.log(`Usage:
264
283
  nota [--no-open] <path/to/plan.md>
284
+ nota init
265
285
  nota init [codex|claude|cursor|all]`);
266
286
  }
267
287
 
268
- async function initAgentInstructions(target) {
269
- const targets = getAgentTargets(target);
270
-
271
- if (!targets) {
272
- console.error('Unknown agent target. Expected codex, claude, cursor, or all.');
273
- process.exit(1);
274
- }
275
-
288
+ async function initAgentInstructions(targets) {
276
289
  await mkdir(resolve(process.cwd(), '.agent-plans'), { recursive: true });
277
290
  await writeFile(resolve(process.cwd(), '.agent-plans', '.gitkeep'), '', { flag: 'a' });
278
291
 
@@ -285,14 +298,38 @@ async function initAgentInstructions(target) {
285
298
  }
286
299
 
287
300
  function getAgentTargets(target) {
288
- const allTargets = ['codex', 'claude', 'cursor'];
289
-
290
- if (target === 'all') return allTargets;
291
- if (allTargets.includes(target)) return [target];
301
+ if (target === 'all') return AGENT_TARGETS;
302
+ if (AGENT_TARGETS.includes(target)) return [target];
292
303
 
293
304
  return null;
294
305
  }
295
306
 
307
+ async function promptForAgentTargets() {
308
+ if (!process.stdin.isTTY) {
309
+ console.error('Interactive init requires a TTY. Use `nota init all` or `nota init <agent>`.');
310
+ process.exit(1);
311
+ }
312
+
313
+ try {
314
+ const { checkbox } = await import('@inquirer/prompts');
315
+
316
+ return await checkbox({
317
+ message: 'Select AI agents to support',
318
+ choices: AGENT_TARGETS.map((agent) => ({
319
+ name: AGENT_LABELS[agent],
320
+ value: agent,
321
+ })),
322
+ });
323
+ } catch (error) {
324
+ if (error?.name === 'ExitPromptError') {
325
+ console.log('\nInitialization cancelled.');
326
+ process.exit(1);
327
+ }
328
+
329
+ throw error;
330
+ }
331
+ }
332
+
296
333
  async function writeAgentInstructions(agent) {
297
334
  const filePath = resolve(process.cwd(), getAgentInstructionPath(agent));
298
335
  const block = getAgentInstructionBlock(agent);
@@ -309,61 +346,6 @@ function getAgentInstructionPath(agent) {
309
346
  return join('.cursor', 'rules', 'nota.mdc');
310
347
  }
311
348
 
312
- function getAgentInstructionBlock() {
313
- return `<!-- nota:start -->
314
- ## Plan Annotation Workflow
315
-
316
- When the user asks for an implementation plan, create a Markdown file in:
317
-
318
- \`\`\`text
319
- .agent-plans/<date>-<topic>.md
320
- \`\`\`
321
-
322
- Then launch the local annotation tool.
323
-
324
- On Windows/PowerShell, use the npm \`.cmd\` shim explicitly:
325
-
326
- \`\`\`powershell
327
- nota.cmd .agent-plans/<file>.md
328
- \`\`\`
329
-
330
- Do not use \`Start-Process -FilePath nota\` on Windows/PowerShell; it can resolve to the extensionless npm shim and trigger the Windows "open with" dialog instead of running the CLI.
331
-
332
- On macOS/Linux:
333
-
334
- \`\`\`bash
335
- nota .agent-plans/<file>.md
336
- \`\`\`
337
-
338
- Tell the user that the plan is open for annotation. Do not continue with implementation until the user says they are done annotating.
339
-
340
- After the user is done, read the same Markdown file again. User feedback is stored as CriticMarkup:
341
-
342
- \`\`\`md
343
- {==text being discussed==}{>>user comment<<}
344
- \`\`\`
345
-
346
- Treat those comments as direct user feedback. Update the plan or implementation accordingly.
347
-
348
- Once all annotations have been processed, remove every CriticMarkup annotation from the file, as well as the global annotations block if present:
349
-
350
- \`\`\`md
351
- <!-- nota:globals
352
- [...]
353
- -->
354
- \`\`\`
355
-
356
- Leave only the plain Markdown content.
357
-
358
- If the \`nota\` command is unavailable, tell the user to install it globally:
359
-
360
- \`\`\`bash
361
- npm install -g nota
362
- \`\`\`
363
- <!-- nota:end -->
364
- `;
365
- }
366
-
367
349
  async function upsertMarkedBlock(filePath, block) {
368
350
  const startMarker = '<!-- nota:start -->';
369
351
  const endMarker = '<!-- nota:end -->';
@@ -0,0 +1 @@
1
+ :root{color:#18202f;font-synthesis:none;text-rendering:optimizelegibility;background:#f4f6f8;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}*{box-sizing:border-box}body{min-width:320px;height:100vh;margin:0;overflow:hidden}button{color:#18202f;cursor:pointer;font:inherit;background:#fff;border:1px solid #c6ccd6;border-radius:6px;min-height:32px;padding:0 12px;font-weight:650}button:hover:not(:disabled){border-color:#7a8799}button:disabled{cursor:not-allowed;opacity:.55}button.primary{color:#fff;background:#215f88;border-color:#215f88}button.done{color:#fff;background:#1a6b3c;border-color:#1a6b3c}button.done:hover:not(:disabled){background:#145230;border-color:#145230}.app-shell{flex-direction:column;height:100vh;display:flex;overflow:hidden}.topbar{background:#fff;border-bottom:1px solid #d9dee7;justify-content:space-between;align-items:center;gap:16px;padding:10px 20px;display:flex}.eyebrow{color:#647084;text-transform:uppercase;margin:0 0 4px;font-size:12px;font-weight:750}.topbar h1,.comment-pane-header h2{margin:0}.topbar h1{font-size:20px}.comment-pane-header h2{font-size:16px}.file-actions{align-items:center;gap:10px;display:flex}.copy-plan-button{min-width:132px}.hidden-input{display:none}.save-status{color:#647084;white-space:nowrap;font-size:13px;font-weight:650}.workspace{flex:1;grid-template-columns:minmax(0,1fr) minmax(280px,360px);min-height:0;display:grid;overflow:hidden}.editor-pane,.comment-pane{min-width:0;min-height:0;padding:20px}.editor-pane{border-right:1px solid #d9dee7;flex-direction:column;display:flex}.editor-frame{background:#fff;border:1px solid #d9dee7;border-radius:8px;flex:1;width:100%;max-width:90ch;min-height:0;margin:0 auto;overflow:auto}.tiptap{outline:none;min-height:100%;padding:28px}.tiptap>:first-child{margin-top:0}.tiptap p{line-height:1.65}.tiptap h1,.tiptap h2,.tiptap h3,.tiptap h4,.tiptap h5,.tiptap h6{color:#121a27;margin:1.4em 0 .55em;font-weight:750;line-height:1.25}.tiptap h1{font-size:2rem}.tiptap h2{font-size:1.6rem}.tiptap h3{font-size:1.35rem}.tiptap h4{font-size:1.15rem}.tiptap h5{font-size:1rem}.tiptap h6{color:#48556a;text-transform:uppercase;font-size:.9rem}.tiptap code{color:#202b3c;background:#eef2f6;border-radius:4px;padding:2px 4px;font-family:Cascadia Code,SFMono-Regular,Consolas,Liberation Mono,monospace;font-size:.9em}.tiptap pre{color:#f5f7fb;background:#18202f;border:1px solid #2d3748;border-radius:8px;margin:18px 0;padding:14px 16px;line-height:1.6;overflow-x:auto}.tiptap pre code{color:inherit;white-space:pre;background:0 0;border-radius:0;padding:0;font-size:13px;display:block}.tiptap mark[data-comment]{cursor:text;background:#fff0a6;border-bottom:2px solid #d29a1f;border-radius:3px;padding:1px 2px}.tiptap .pending-annotation{background:#d8ebff;border-bottom:2px solid #3978b8;border-radius:3px;padding:1px 2px}.tiptap pre mark[data-comment],.tiptap pre .pending-annotation{color:#18202f}.comment-pane{background:#fbfcfd;flex-direction:column;gap:16px;display:flex;overflow:hidden}.comment-pane-header{justify-content:space-between;align-items:center;display:flex}.comment-pane-actions{align-items:center;gap:8px;display:flex}.comment-pane-header span{color:#48556a;text-align:center;background:#e7ecf2;border-radius:999px;min-width:28px;padding:4px 8px;font-size:12px;font-weight:700}.small-button{min-height:32px;padding:0 10px;font-size:13px}.comment-list{flex-direction:column;flex:1;gap:12px;min-height:0;padding-right:2px;display:flex;overflow:auto}.comment-item{background:#fff;border:1px solid #d9dee7;border-radius:8px;flex-direction:column;gap:10px;padding:12px;display:flex}.comment-item.active{border-color:#215f88;box-shadow:0 0 0 3px #215f8824}.comment-item-header{align-items:flex-start;gap:8px;display:flex}.comment-target{color:#3d3320;text-align:left;text-overflow:ellipsis;white-space:nowrap;background:#f7f2d0;border:0;flex:1;min-width:0;min-height:0;padding:8px;font-size:13px;font-weight:700;line-height:1.35;display:block;overflow:hidden}.global-comment-target{color:#25455f;background:#e8f1f8}.delete-comment-button{color:#8f2d2d;flex:none;min-height:30px;padding:0 8px;font-size:12px}.delete-comment-button:hover:not(:disabled){border-color:#b24545}textarea{color:#18202f;font:inherit;resize:vertical;border:1px solid #c6ccd6;border-radius:8px;width:100%;min-height:96px;padding:12px;line-height:1.5}textarea:focus{border-color:#215f88;outline:3px solid #215f882e}.empty-state{color:#647084;margin:0;line-height:1.5}.modal-backdrop{z-index:20;background:#121a2775;justify-content:center;align-items:center;padding:20px;display:flex;position:fixed;inset:0}.modal{background:#fff;border-radius:8px;flex-direction:column;gap:14px;width:min(100%,520px);max-width:520px;padding:20px;display:flex;box-shadow:0 24px 80px #121a2747}.modal textarea{min-height:180px}.modal-actions{justify-content:flex-end;gap:10px;display:flex}@media (width<=800px){.topbar{flex-direction:column;align-items:flex-start}.workspace{grid-template-rows:minmax(0,1fr) minmax(220px,36vh);grid-template-columns:1fr}.editor-pane{border-right:0}.comment-pane{border-top:1px solid #d9dee7}}