gitwrit 0.2.8 → 0.3.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
@@ -161,13 +161,19 @@ If that works, restart gitwrit with `gitwrit restart`. If it fails, fix your Git
161
161
 
162
162
  ---
163
163
 
164
- ## Why not Google Docs, Notion, or Obsidian Sync?
164
+ ## Why not cloud-based writing tools?
165
165
 
166
- Those tools store your files on their infrastructure. gitwrit does not. Your writing lives in a plain Git repository that you control, pushed wherever you want—GitHub, GitLab, a private server, anywhere that accepts a Git remote.
166
+ Most cloud-based writing and note-taking tools store your files on their
167
+ infrastructure. gitwrit does not. Your writing lives in a plain Git repository
168
+ that you control, pushed wherever you want—GitHub, GitLab, a private server,
169
+ anywhere that accepts a Git remote.
167
170
 
168
- Just to be clear, there is absolutely nothing wrong with using any of these tools. I use them!
171
+ Just to be clear, there is absolutely nothing wrong with using cloud-based
172
+ tools. Many people use them every day for good reason!
169
173
 
170
- But I thought making this might be especially useful for proprietary documentation like internal specs, research notes, model cards, experiment logs, etc. For when putting files into a third-party cloud is not an option, or when you want to keep everything essential to your codebase **all in one place**.
174
+ But gitwrit might be especially useful when cloud storage isn't an option—for
175
+ proprietary documentation like internal specs, research notes, model cards,
176
+ experiment logs, etc. For when putting files into a third-party cloud is not an option, or when you want to keep everything essential to your codebase all in one place.
171
177
 
172
178
  ---
173
179
 
package/bin/gitwrit.js CHANGED
@@ -14,6 +14,7 @@ import { add } from '../src/commands/add.js';
14
14
  import { remove } from '../src/commands/remove.js';
15
15
  import { logs } from '../src/commands/logs.js';
16
16
  import { help } from '../src/commands/help.js';
17
+ import { demo } from '../src/commands/demo.js';
17
18
  import { checkForUpdate, currentVersion } from '../src/updater.js';
18
19
  import chalk from 'chalk';
19
20
  import { TEAL, PINK } from '../src/ui.js';
@@ -74,6 +75,10 @@ program.command('help')
74
75
  .description('List all available commands')
75
76
  .action(help);
76
77
 
78
+ program.command('demo')
79
+ .description('Preview gitwrit\'s UI with placeholder data')
80
+ .action(demo);
81
+
77
82
  // ── hidden daemon command ─────────────────────────────────────────────────────
78
83
  program.command('__daemon', { hidden: true })
79
84
  .action(() => import('../src/daemon.js'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitwrit",
3
- "version": "0.2.8",
3
+ "version": "0.3.0",
4
4
  "description": "Private, versioned writing for people who live in the terminal.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -43,9 +43,11 @@ async function editField(key, currentValue) {
43
43
  { name: '5 seconds', value: 5000 },
44
44
  { name: '10 seconds', value: 10000 },
45
45
  { name: 'Custom', value: 'custom' },
46
+ { name: 'Exit', value: 'exit' },
46
47
  ],
47
48
  default: currentValue,
48
49
  });
50
+ if (choice === 'exit') return currentValue; // no change
49
51
  if (choice === 'custom') {
50
52
  const raw = await input({ message: 'Seconds to wait:' });
51
53
  return parseFloat(raw) * 1000;
@@ -61,9 +63,11 @@ async function editField(key, currentValue) {
61
63
  { name: 'Every 10 minutes', value: 600000 },
62
64
  { name: 'Every 30 minutes', value: 1800000 },
63
65
  { name: 'Custom', value: 'custom' },
66
+ { name: 'Exit', value: 'exit' },
64
67
  ],
65
68
  default: currentValue,
66
69
  });
70
+ if (choice === 'exit') return currentValue; // no change
67
71
  if (choice === 'custom') {
68
72
  const raw = await input({ message: 'Minutes between pushes:' });
69
73
  return parseFloat(raw) * 60000;
@@ -72,14 +76,17 @@ async function editField(key, currentValue) {
72
76
  }
73
77
 
74
78
  if (key === 'branchMode') {
75
- return select({
79
+ const choice = await select({
76
80
  message: 'Default branch mode?',
77
81
  choices: [
78
82
  { name: "Current branch — commit to whatever branch you're on", value: 'current' },
79
83
  { name: 'Autogenerated — create a fresh named branch each session', value: 'autogenerated' },
84
+ { name: 'Exit', value: 'exit' },
80
85
  ],
81
86
  default: currentValue,
82
87
  });
88
+ if (choice === 'exit') return currentValue; // no change
89
+ return choice;
83
90
  }
84
91
  }
85
92
 
@@ -112,10 +119,11 @@ async function configureGlobal() {
112
119
  choices: [
113
120
  ...FIELDS.map(f => ({ name: f.label, value: f.key })),
114
121
  { name: 'Done', value: null },
122
+ { name: 'Exit', value: 'exit' },
115
123
  ],
116
124
  });
117
125
 
118
- if (!field) return;
126
+ if (!field || field === 'exit') return;
119
127
 
120
128
  const newValue = await editField(field, config[field]);
121
129
  config[field] = newValue;
@@ -167,10 +175,13 @@ async function configureLocal(dir) {
167
175
  { name: 'Edit an override', value: 'edit' },
168
176
  { name: 'Remove an override', value: 'remove' },
169
177
  { name: 'Add another override', value: 'add' },
178
+ { name: 'Exit', value: 'exit' },
170
179
  ],
171
180
  })
172
181
  : 'add';
173
182
 
183
+ if (action === 'exit') return;
184
+
174
185
  if (action === 'edit' || action === 'add') {
175
186
  const overriddenKeys = Object.keys(local);
176
187
  const choices = action === 'edit'
@@ -184,9 +195,14 @@ async function configureLocal(dir) {
184
195
 
185
196
  const field = await select({
186
197
  message: 'Which setting?',
187
- choices: choices.map(f => ({ name: f.label, value: f.key })),
198
+ choices: [
199
+ ...choices.map(f => ({ name: f.label, value: f.key })),
200
+ { name: 'Exit', value: 'exit' },
201
+ ],
188
202
  });
189
203
 
204
+ if (field === 'exit') return;
205
+
190
206
  const currentValue = local[field] ?? global[field];
191
207
  const newValue = await editField(field, currentValue);
192
208
  local[field] = newValue;
@@ -199,11 +215,16 @@ async function configureLocal(dir) {
199
215
  const overriddenKeys = Object.keys(local);
200
216
  const field = await select({
201
217
  message: 'Which override would you like to remove?',
202
- choices: FIELDS
203
- .filter(f => overriddenKeys.includes(f.key))
204
- .map(f => ({ name: f.label, value: f.key })),
218
+ choices: [
219
+ ...FIELDS
220
+ .filter(f => overriddenKeys.includes(f.key))
221
+ .map(f => ({ name: f.label, value: f.key })),
222
+ { name: 'Exit', value: 'exit' },
223
+ ],
205
224
  });
206
225
 
226
+ if (field === 'exit') return;
227
+
207
228
  delete local[field];
208
229
  await saveLocalConfig(dir, local);
209
230
  print.gap();
@@ -235,9 +256,15 @@ export async function config() {
235
256
  choices: [
236
257
  { name: 'Global defaults', value: 'global' },
237
258
  { name: `This directory (${dir})`, value: 'local' },
259
+ { name: 'Exit', value: 'exit' },
238
260
  ],
239
261
  });
240
262
 
263
+ if (scope === 'exit') {
264
+ print.gap();
265
+ return;
266
+ }
267
+
241
268
  print.gap();
242
269
 
243
270
  if (scope === 'global') {
@@ -247,4 +274,4 @@ export async function config() {
247
274
  }
248
275
 
249
276
  print.gap();
250
- }
277
+ }
@@ -0,0 +1,81 @@
1
+ import boxen from 'boxen';
2
+ import chalk from 'chalk';
3
+ import { print, TEAL, PINK } from '../ui.js';
4
+ import { renderBanner } from '../banner.js';
5
+
6
+ // ─── demo ─────────────────────────────────────────────────────────────────────
7
+ //
8
+ // renders gitwrit's UI with placeholder data — no real paths, no real usernames.
9
+ // useful for screenshots, documentation, and promo material.
10
+ //
11
+ // shows three screens in sequence:
12
+ // 1. gitwrit start output
13
+ // 2. gitwrit status output
14
+ // 3. gitwrit stop output
15
+
16
+ function boxRow(label, value) {
17
+ return chalk.dim(` ${label.padEnd(13)}`) + chalk.white(value);
18
+ }
19
+
20
+ const BOX_OPTS = {
21
+ padding: { top: 0, bottom: 0, left: 1, right: 2 },
22
+ borderStyle: 'round',
23
+ borderColor: TEAL,
24
+ };
25
+
26
+ export function demo() {
27
+ // ── start ──────────────────────────────────────────────────────────────────
28
+
29
+ renderBanner();
30
+
31
+ print.good('gitwrit is running.');
32
+ print.gap();
33
+ print.row('Watching', '~/notes, ~/projects/docs');
34
+ print.row('Files', '.md, .mdx');
35
+ print.row('Branch', 'Current branch');
36
+ print.row('Committing', '3s after last save');
37
+ print.row('Pushing', 'Every 5 min');
38
+ print.gap();
39
+ print.hint('Type all you want, we\'ve got you.');
40
+ print.gap();
41
+
42
+ // ── status ─────────────────────────────────────────────────────────────────
43
+
44
+ const statusLines = [
45
+ '',
46
+ chalk.hex(TEAL).bold(' gitwrit is running.'),
47
+ '',
48
+ boxRow('Uptime', '42m'),
49
+ boxRow('Watching', '~/notes, ~/projects/docs'),
50
+ boxRow('Branch', 'Current branch'),
51
+ boxRow('Commits', '12 this session'),
52
+ boxRow('Last commit', '26s ago (ideas.md)'),
53
+ boxRow('Last push', '2m ago'),
54
+ '',
55
+ ];
56
+
57
+ console.log(boxen(statusLines.join('\n'), BOX_OPTS));
58
+ print.gap();
59
+
60
+ // ── stop ───────────────────────────────────────────────────────────────────
61
+
62
+ const stopLines = [
63
+ chalk.hex(TEAL).bold(' gitwrit stopped.'),
64
+ '',
65
+ chalk.dim(' Committed ') + chalk.white('12 times this session'),
66
+ chalk.dim(' Last push ') + chalk.white('2m ago'),
67
+ '',
68
+ chalk.dim(' Your work is safe. See you next time.'),
69
+ ];
70
+
71
+ console.log(
72
+ boxen(stopLines.join('\n'), {
73
+ padding: { top: 0, bottom: 0, left: 1, right: 2 },
74
+ borderStyle: 'round',
75
+ borderColor: TEAL,
76
+ })
77
+ );
78
+ print.gap();
79
+ print.hint('This is a demo. Run gitwrit start to begin a real session.');
80
+ print.gap();
81
+ }
@@ -17,6 +17,7 @@ const commands = [
17
17
  { name: 'remove', desc: 'Remove a directory from your watch list' },
18
18
  { name: '', desc: '' },
19
19
  { name: 'help', desc: 'List all available commands' },
20
+ { name: 'demo', desc: 'Preview gitwrit\'s UI with placeholder data' },
20
21
  ];
21
22
 
22
23
  const tips = [
@@ -25,6 +26,7 @@ const tips = [
25
26
  'gitwrit commits to whatever branch you\'re on — switch branches freely.',
26
27
  'Branch mode "autogenerated" names your session branch automatically.',
27
28
  'gitwrit requires a configured Git remote to push your work offsite.',
29
+ 'Run gitwrit demo to preview the UI with placeholder data — great for screenshots.',
28
30
  ];
29
31
 
30
32
  export function help() {
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'url';
4
4
  import { dirname, join } from 'path';
5
5
  import ora from 'ora';
6
6
 
7
- import { print } from '../ui.js';
7
+ import { print, displayPath } from '../ui.js';
8
8
  import { renderBanner } from '../banner.js';
9
9
  import { PID_FILE, GITWRIT_DIR } from '../paths.js';
10
10
  import { globalConfigExists, loadGlobalConfig } from '../settings.js';
@@ -103,7 +103,7 @@ export async function start() {
103
103
  spinner.succeed(wasUnclean ? 'Picked up where you left off.' : 'gitwrit is running.');
104
104
 
105
105
  print.gap();
106
- print.row('Watching', config.watch.map(w => w.path).join(', '));
106
+ print.row('Watching', config.watch.map(w => displayPath(w.path)).join(', '));
107
107
  print.row('Files', config.fileTypes.join(', '));
108
108
 
109
109
  if (sessionBranch) {
@@ -1,7 +1,7 @@
1
1
  import { readFile } from 'fs/promises';
2
2
  import boxen from 'boxen';
3
3
  import chalk from 'chalk';
4
- import { print, timeAgo, TEAL, PINK } from '../ui.js';
4
+ import { print, timeAgo, TEAL, PINK, displayPath } from '../ui.js';
5
5
  import { PID_FILE } from '../paths.js';
6
6
  import { readState, STATE } from '../state.js';
7
7
  import { loadGlobalConfig, globalConfigExists } from '../settings.js';
@@ -105,7 +105,7 @@ export async function status() {
105
105
 
106
106
  lines.push('');
107
107
  lines.push(row('Uptime', formatUptime(state.startedAt)));
108
- lines.push(row('Watching', config.watch.map(w => w.path).join(', ')));
108
+ lines.push(row('Watching', config.watch.map(w => displayPath(w.path)).join(', ')));
109
109
 
110
110
  if (state.sessionBranch) {
111
111
  lines.push(row('Branch', state.sessionBranch) + chalk.hex(PINK).dim(' (autogenerated)'));
package/src/ui.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import chalk from 'chalk';
2
+ import { homedir } from 'os';
2
3
 
3
4
  // ─── palette ──────────────────────────────────────────────────────────────────
4
5
 
@@ -59,6 +60,14 @@ export function timeAgo(date) {
59
60
  return `${Math.floor(seconds / 86400)}d ago`;
60
61
  }
61
62
 
63
+ // ─── path display ─────────────────────────────────────────────────────────────
64
+
65
+ // replaces the full home directory path with ~ for cleaner, safer output.
66
+ // e.g. /Users/terrancebiddle/Documents/notes → ~/Documents/notes
67
+ export function displayPath(p) {
68
+ return p.replace(homedir(), '~');
69
+ }
70
+
62
71
  // ─── ms formatting ────────────────────────────────────────────────────────────
63
72
 
64
73
  export function formatMs(ms) {