gipity 1.0.365 → 1.0.374

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 (52) hide show
  1. package/dist/banner.js +3 -1
  2. package/dist/commands/add.js +2 -2
  3. package/dist/commands/agent.js +3 -5
  4. package/dist/commands/approval.js +3 -3
  5. package/dist/commands/audit.js +2 -2
  6. package/dist/commands/chat.js +4 -4
  7. package/dist/commands/claude.js +8 -9
  8. package/dist/commands/credits.js +1 -1
  9. package/dist/commands/db.js +7 -6
  10. package/dist/commands/deploy.js +5 -8
  11. package/dist/commands/doctor.js +11 -13
  12. package/dist/commands/domain.js +18 -15
  13. package/dist/commands/email.js +0 -4
  14. package/dist/commands/fn.js +2 -2
  15. package/dist/commands/generate.js +57 -5
  16. package/dist/commands/job.js +6 -6
  17. package/dist/commands/location.js +7 -7
  18. package/dist/commands/login.js +2 -16
  19. package/dist/commands/logout.js +2 -3
  20. package/dist/commands/logs.js +1 -1
  21. package/dist/commands/page-eval.js +6 -4
  22. package/dist/commands/page-fetch.js +136 -0
  23. package/dist/commands/page-inspect.js +29 -27
  24. package/dist/commands/page-screenshot.js +17 -18
  25. package/dist/commands/page-test.js +8 -8
  26. package/dist/commands/page.js +6 -3
  27. package/dist/commands/plan.js +4 -4
  28. package/dist/commands/push.js +2 -6
  29. package/dist/commands/realtime.js +7 -9
  30. package/dist/commands/relay-install.js +18 -21
  31. package/dist/commands/relay.js +29 -31
  32. package/dist/commands/sandbox.js +16 -3
  33. package/dist/commands/service.js +1 -3
  34. package/dist/commands/status.js +2 -2
  35. package/dist/commands/sync.js +4 -1
  36. package/dist/commands/test.js +7 -13
  37. package/dist/commands/text.js +10 -10
  38. package/dist/commands/uninstall.js +20 -42
  39. package/dist/commands/update.js +0 -2
  40. package/dist/commands/upload.js +4 -4
  41. package/dist/commands/workflow.js +1 -2
  42. package/dist/helpers/output.js +45 -7
  43. package/dist/index.js +4 -0
  44. package/dist/knowledge.js +19 -4
  45. package/dist/progress.js +60 -0
  46. package/dist/project-setup.js +5 -1
  47. package/dist/provider-docs.js +7 -7
  48. package/dist/setup.js +20 -7
  49. package/dist/sync.js +16 -6
  50. package/dist/updater/shim.js +18 -4
  51. package/dist/upload.js +6 -0
  52. package/package.json +5 -4
@@ -8,7 +8,7 @@ import { planFor, UnsupportedPlatformError } from '../relay/installers.js';
8
8
  function requirePaired() {
9
9
  const device = state.getDevice();
10
10
  if (!device) {
11
- console.error(` ${clrError('No paired device.')} Run ${bold('gipity claude')} to pair this machine.`);
11
+ console.error(`${clrError('No paired device.')} Run ${bold('gipity claude')} to pair this machine.`);
12
12
  process.exit(1);
13
13
  }
14
14
  return device;
@@ -48,40 +48,37 @@ export function registerInstallCommands(relayCommand) {
48
48
  }
49
49
  catch (err) {
50
50
  if (err instanceof UnsupportedPlatformError) {
51
- console.error(` ${clrError(err.message)}`);
52
- console.error(` ${dim('Supported on macOS, Linux, and Windows.')}`);
51
+ console.error(clrError(err.message));
52
+ console.error(muted('Supported on macOS, Linux, and Windows.'));
53
53
  }
54
54
  else
55
55
  throw err;
56
56
  process.exit(1);
57
57
  }
58
- console.log('');
59
- console.log(` ${bold('Install plan:')} ${plan.summary}`);
60
- console.log(` ${bold('File:')} ${plan.path}`);
61
- console.log('');
58
+ console.log(`${bold('Install plan:')} ${plan.summary}`);
59
+ console.log(`${bold('File:')} ${plan.path}`);
62
60
  if (opts.print) {
61
+ console.log('');
63
62
  console.log(`${dim('--- file content ---')}`);
64
63
  console.log(plan.content);
65
64
  console.log(`${dim('--- enable: ---')}`);
66
65
  console.log(plan.enableDisplay);
67
- console.log('');
68
66
  return;
69
67
  }
70
- if (!(await confirm(' Write the file and enable the service now?'))) {
71
- console.log(` ${muted('Cancelled. (Use --print to preview without installing.)')}`);
68
+ if (!(await confirm('Write the file and enable the service now?'))) {
69
+ console.log(muted('Cancelled. (Use --print to preview without installing.)'));
72
70
  return;
73
71
  }
74
72
  mkdirSync(dirname(plan.path), { recursive: true });
75
73
  writeFileSync(plan.path, plan.content);
76
- console.log(` ${success(`Wrote ${plan.path}`)}`);
74
+ console.log(success(`Wrote ${plan.path}`));
77
75
  if (!runArgvSequence(plan.enableCmds, { failFast: true })) {
78
- console.error(`\n ${clrError(`Couldn't enable autostart. Try manually: ${plan.enableDisplay}`)}`);
76
+ console.error(clrError(`Couldn't enable autostart. Try manually: ${plan.enableDisplay}`));
79
77
  process.exit(1);
80
78
  }
81
- console.log('');
82
- console.log(` ${success('Background service installed and started.')}`);
83
- console.log(` ${dim(`Check status: ${plan.statusDisplay}`)}`);
84
- console.log(` ${dim(`Tail logs: gipity relay log`)}`);
79
+ console.log(success('Background service installed and started.'));
80
+ console.log(muted(`Check status: ${plan.statusDisplay}`));
81
+ console.log(muted(`Tail logs: gipity relay log`));
85
82
  });
86
83
  relayCommand
87
84
  .command('autostart <on|off>')
@@ -89,7 +86,7 @@ export function registerInstallCommands(relayCommand) {
89
86
  .action(async (mode) => {
90
87
  const want = mode.toLowerCase();
91
88
  if (want !== 'on' && want !== 'off') {
92
- console.error(` ${clrError('Usage: gipity relay autostart <on|off>')}`);
89
+ console.error(clrError('Usage: gipity relay autostart <on|off>'));
93
90
  process.exit(1);
94
91
  }
95
92
  let plan;
@@ -98,7 +95,7 @@ export function registerInstallCommands(relayCommand) {
98
95
  }
99
96
  catch (err) {
100
97
  if (err instanceof UnsupportedPlatformError) {
101
- console.error(` ${clrError(err.message)}`);
98
+ console.error(clrError(err.message));
102
99
  process.exit(1);
103
100
  }
104
101
  else
@@ -106,14 +103,14 @@ export function registerInstallCommands(relayCommand) {
106
103
  }
107
104
  const cmds = want === 'on' ? plan.enableCmds : plan.disableCmds;
108
105
  const display = want === 'on' ? plan.enableDisplay : plan.disableDisplay;
109
- console.log(` ${info('Running:')} ${dim(display)}`);
106
+ console.log(`${info('Running:')} ${dim(display)}`);
110
107
  // Disable is best-effort (the task may already be stopped); enable is fail-fast.
111
108
  const ok = runArgvSequence(cmds, { failFast: want === 'on' });
112
109
  if (!ok && want === 'on') {
113
- console.error(`\n ${clrError('Command failed.')}`);
110
+ console.error(clrError('Command failed.'));
114
111
  process.exit(1);
115
112
  }
116
- console.log(` ${success(`Autostart ${want}.`)}`);
113
+ console.log(success(`Autostart ${want}.`));
117
114
  });
118
115
  }
119
116
  //# sourceMappingURL=relay-install.js.map
@@ -10,7 +10,7 @@ import { existsSync, readFileSync, unlinkSync } from 'fs';
10
10
  import { spawn } from 'child_process';
11
11
  import { post } from '../api.js';
12
12
  import { confirm } from '../utils.js';
13
- import { bold, brand, dim, success, error as clrError, muted, } from '../colors.js';
13
+ import { bold, brand, success, error as clrError, muted, } from '../colors.js';
14
14
  import * as state from '../relay/state.js';
15
15
  import * as daemon from '../relay/daemon.js';
16
16
  import { registerInstallCommands } from './relay-install.js';
@@ -32,16 +32,14 @@ relayCommand
32
32
  console.log(JSON.stringify(safe, null, 2));
33
33
  return;
34
34
  }
35
- console.log('');
36
35
  if (!s.device) {
37
- console.log(` ${muted('No paired device.')} Run ${brand('gipity claude')} to pair this machine.`);
36
+ console.log(`${muted('No paired device.')} Run ${brand('gipity claude')} to pair this machine.`);
38
37
  return;
39
38
  }
40
- console.log(` ${bold('Device:')} ${brand(s.device.name)} ${muted(`(${s.device.guid})`)}`);
41
- console.log(` ${bold('Platform:')} ${s.device.platform}`);
42
- console.log(` ${bold('Paired:')} ${s.device.paired_at}`);
43
- console.log(` ${bold('Paused:')} ${s.paused ? 'yes' : 'no'}`);
44
- console.log('');
39
+ console.log(`${bold('Device:')} ${brand(s.device.name)} ${muted(`(${s.device.guid})`)}`);
40
+ console.log(`${bold('Platform:')} ${s.device.platform}`);
41
+ console.log(`${bold('Paired:')} ${s.device.paired_at}`);
42
+ console.log(`${bold('Paused:')} ${s.paused ? 'yes' : 'no'}`);
45
43
  });
46
44
  // ─── gipity relay run ──────────────────────────────────────────────────
47
45
  relayCommand
@@ -64,12 +62,12 @@ relayCommand
64
62
  .action(async (opts) => {
65
63
  const pidPath = state.getDaemonPidPath();
66
64
  if (!existsSync(pidPath)) {
67
- console.log(` ${muted('Background service isn\'t running.')}`);
65
+ console.log(muted('Background service isn\'t running.'));
68
66
  return;
69
67
  }
70
68
  const pid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10);
71
69
  if (!pid || isNaN(pid)) {
72
- console.error(` ${clrError('PID file is empty or malformed.')}`);
70
+ console.error(clrError('PID file is empty or malformed.'));
73
71
  process.exit(1);
74
72
  }
75
73
  try {
@@ -77,14 +75,14 @@ relayCommand
77
75
  }
78
76
  catch (err) {
79
77
  if (err?.code === 'ESRCH') {
80
- console.log(` ${muted(`PID ${pid} not running - cleaning up stale PID file.`)}`);
78
+ console.log(muted(`PID ${pid} not running - cleaning up stale PID file.`));
81
79
  try {
82
80
  unlinkSync(pidPath);
83
81
  }
84
82
  catch { /* ignore */ }
85
83
  return;
86
84
  }
87
- console.error(` ${clrError(`Could not signal PID ${pid}: ${err?.message || err}`)}`);
85
+ console.error(clrError(`Could not signal PID ${pid}: ${err?.message || err}`));
88
86
  process.exit(1);
89
87
  }
90
88
  // Wait up to 5s for clean shutdown.
@@ -110,15 +108,15 @@ relayCommand
110
108
  process.kill(pid, 'SIGKILL');
111
109
  }
112
110
  catch { /* ignore */ }
113
- console.log(` ${success('Background service force-stopped.')}`);
111
+ console.log(success('Background service force-stopped.'));
114
112
  }
115
113
  else {
116
- console.error(` ${clrError(`Didn't shut down cleanly after 5s. Retry with --force to stop it.`)}`);
114
+ console.error(clrError(`Didn't shut down cleanly after 5s. Retry with --force to stop it.`));
117
115
  process.exit(1);
118
116
  }
119
117
  }
120
118
  else {
121
- console.log(` ${success('Background service stopped.')}`);
119
+ console.log(success('Background service stopped.'));
122
120
  }
123
121
  });
124
122
  // ─── gipity relay pause / resume ───────────────────────────────────────
@@ -128,7 +126,7 @@ relayCommand
128
126
  .action(() => {
129
127
  requirePaired();
130
128
  state.setPaused(true);
131
- console.log(` ${success('Paused.')} ${dim('Run `gipity relay resume` to accept commands again.')}`);
129
+ console.log(`${success('Paused.')} ${muted('Run `gipity relay resume` to accept commands again.')}`);
132
130
  });
133
131
  relayCommand
134
132
  .command('resume')
@@ -136,7 +134,7 @@ relayCommand
136
134
  .action(() => {
137
135
  requirePaired();
138
136
  state.setPaused(false);
139
- console.log(` ${success('Resumed.')}`);
137
+ console.log(success('Resumed.'));
140
138
  });
141
139
  // ─── gipity relay rename <name> ────────────────────────────────────────
142
140
  relayCommand
@@ -146,7 +144,7 @@ relayCommand
146
144
  const device = requirePaired();
147
145
  const name = newName.trim();
148
146
  if (!name || name.length > 100) {
149
- console.error(` ${clrError('Device name must be 1–100 non-whitespace characters.')}`);
147
+ console.error(clrError('Device name must be 1–100 non-whitespace characters.'));
150
148
  process.exit(1);
151
149
  }
152
150
  try {
@@ -154,14 +152,14 @@ relayCommand
154
152
  await post(`/remote-devices/${encodeURIComponent(device.guid)}/rename`, { name });
155
153
  }
156
154
  catch (err) {
157
- console.error(`\n ${clrError(`Rename failed: ${err?.message || err}`)}`);
155
+ console.error(clrError(`Rename failed: ${err?.message || err}`));
158
156
  if (err?.statusCode === 401) {
159
- console.error(` ${dim('Run `gipity login` first - rename requires your user auth.')}`);
157
+ console.error(muted('Run `gipity login` first - rename requires your user auth.'));
160
158
  }
161
159
  process.exit(1);
162
160
  }
163
161
  state.setDevice({ ...device, name });
164
- console.log(` ${success(`Renamed to ${bold(name)}.`)}`);
162
+ console.log(success(`Renamed to ${bold(name)}.`));
165
163
  });
166
164
  // ─── gipity relay revoke ───────────────────────────────────────────────
167
165
  relayCommand
@@ -169,8 +167,8 @@ relayCommand
169
167
  .description('Revoke and forget this device')
170
168
  .action(async () => {
171
169
  const device = requirePaired();
172
- if (!(await confirm(` Revoke ${bold(device.name)} (${device.guid})?`))) {
173
- console.log(` ${muted('Cancelled.')}`);
170
+ if (!(await confirm(`Revoke ${bold(device.name)} (${device.guid})?`))) {
171
+ console.log(muted('Cancelled.'));
174
172
  return;
175
173
  }
176
174
  try {
@@ -179,12 +177,12 @@ relayCommand
179
177
  catch (err) {
180
178
  // Even if the server call fails, drop local state - a stale token is
181
179
  // worse than double-revoking. Warn loudly though.
182
- console.error(` ${clrError(`Server revoke failed: ${err?.message || err}`)}`);
183
- console.error(` ${dim('Local token cleared anyway. Visit the web CLI to confirm the server-side revoke.')}`);
180
+ console.error(clrError(`Server revoke failed: ${err?.message || err}`));
181
+ console.error(muted('Local token cleared anyway. Visit the web CLI to confirm the server-side revoke.'));
184
182
  }
185
183
  state.clearDevice();
186
- console.log(` ${success('Device revoked + local state cleared.')}`);
187
- console.log(` ${dim('Any running background service will notice and exit within ~30s.')}`);
184
+ console.log(success('Device revoked + local state cleared.'));
185
+ console.log(muted('Any running background service will notice and exit within ~30s.'));
188
186
  });
189
187
  // ─── gipity relay log ──────────────────────────────────────────────────
190
188
  relayCommand
@@ -195,7 +193,7 @@ relayCommand
195
193
  .action((opts) => {
196
194
  const path = daemon.RELAY_LOG_PATH;
197
195
  if (!existsSync(path)) {
198
- console.log(` ${muted('No log file yet. Start the service with `gipity relay run` (or install it).')}`);
196
+ console.log(muted('No log file yet. Start the service with `gipity relay run` (or install it).'));
199
197
  return;
200
198
  }
201
199
  const lines = parseInt(opts.lines, 10) || 100;
@@ -205,14 +203,14 @@ relayCommand
205
203
  process.stdout.write(tail);
206
204
  }
207
205
  catch (err) {
208
- console.error(` ${clrError(`Could not read log: ${err?.message || err}`)}`);
206
+ console.error(clrError(`Could not read log: ${err?.message || err}`));
209
207
  process.exit(1);
210
208
  }
211
209
  if (opts.follow) {
212
210
  // Defer real follow to `tail -f` - cross-platform fallback below.
213
211
  const tailCmd = process.platform === 'win32' ? null : 'tail';
214
212
  if (!tailCmd) {
215
- console.error(` ${clrError('--follow is not supported on this platform yet.')}`);
213
+ console.error(clrError('--follow is not supported on this platform yet.'));
216
214
  process.exit(1);
217
215
  }
218
216
  const child = spawn(tailCmd, ['-f', '-n', '0', path], { stdio: 'inherit' });
@@ -226,7 +224,7 @@ registerInstallCommands(relayCommand);
226
224
  function requirePaired() {
227
225
  const device = state.getDevice();
228
226
  if (!device) {
229
- console.error(` ${clrError('No paired device.')} Run ${brand('gipity claude')} to pair this machine.`);
227
+ console.error(`${clrError('No paired device.')} Run ${brand('gipity claude')} to pair this machine.`);
230
228
  process.exit(1);
231
229
  }
232
230
  return device;
@@ -2,6 +2,7 @@ import { Command } from 'commander';
2
2
  import { dirname, relative } from 'path';
3
3
  import { post } from '../api.js';
4
4
  import { resolveProjectContext, getConfigPath } from '../config.js';
5
+ import { sync } from '../sync.js';
5
6
  import { error as clrError, dim } from '../colors.js';
6
7
  import { run } from '../helpers/index.js';
7
8
  const LANG_MAP = {
@@ -41,6 +42,9 @@ file you write lands back in the project. No manual copy needed.
41
42
  Use --input only for projects over the auto-mirror cap, or when you want
42
43
  to restrict what the sandbox sees.
43
44
 
45
+ No network, and the toolset is fixed - pip/npm/apt installs won't work.
46
+ Use only preinstalled tools, or pull external inputs into the project first.
47
+
44
48
  Examples:
45
49
 
46
50
  # Auto-mirror: code sees the whole project at /work/
@@ -80,8 +84,17 @@ GCC/Rust).
80
84
  input_files: opts.input,
81
85
  cwd,
82
86
  });
87
+ // Pull sandbox-written outputs down to the local cwd automatically. The
88
+ // server has already mirrored them into the project (VFS) and handed back
89
+ // the exact list, so honoring it here means files land locally without a
90
+ // manual `gipity sync` - same auto-pull contract `gipity chat` uses on its
91
+ // `filesChanged` flag. Skip in one-off mode (no local project to sync into).
92
+ const pulledLocal = !!(res.data.outputFiles?.length && getConfigPath());
93
+ if (pulledLocal) {
94
+ await sync({ interactive: false });
95
+ }
83
96
  if (opts.json) {
84
- console.log(JSON.stringify(res.data));
97
+ console.log(JSON.stringify({ ...res.data, filesSynced: pulledLocal }));
85
98
  }
86
99
  else {
87
100
  if (res.data.autoMirrorSkipped) {
@@ -94,9 +107,9 @@ GCC/Rust).
94
107
  if (res.data.timedOut)
95
108
  console.error(`[Timed out after ${res.data.durationMs}ms]`);
96
109
  if (res.data.outputFiles && res.data.outputFiles.length > 0) {
97
- console.log(`\nOutput files saved to project:`);
110
+ console.log(`\nOutput files ${pulledLocal ? 'synced to this directory' : 'saved to project'}:`);
98
111
  for (const f of res.data.outputFiles)
99
- console.log(` ${f}`);
112
+ console.log(`${f}`);
100
113
  }
101
114
  if (res.data.exitCode !== 0)
102
115
  process.exit(res.data.exitCode);
@@ -27,14 +27,12 @@ serviceCommand
27
27
  .action(() => run('Services', async () => {
28
28
  requireConfig();
29
29
  const width = SERVICES.reduce((m, s) => Math.max(m, s.name.length), 0);
30
- console.log('');
31
30
  console.log('Call one with `gipity service call <name> \'{"json":"body"}\'`');
32
31
  console.log(muted('(GET endpoints like llm/models, tts/voices take --get and no body)'));
33
32
  console.log('');
34
33
  for (const s of SERVICES) {
35
- console.log(` ${bold(s.name.padEnd(width))} ${muted(`[${s.method}] ${s.desc}`)}`);
34
+ console.log(`${bold(s.name.padEnd(width))} ${muted(`[${s.method}] ${s.desc}`)}`);
36
35
  }
37
- console.log('');
38
36
  }));
39
37
  serviceCommand
40
38
  .command('call <name> [body]')
@@ -86,8 +86,8 @@ export const statusCommand = new Command('status')
86
86
  }
87
87
  else {
88
88
  console.log(`${muted('Hooks:')} ${warning(`missing/modified: ${hookCheck.missing.join(', ')}`)}`);
89
- console.log(` ${muted('Run `gipity status --repair-hooks` to re-install.')}`);
90
- console.log(` ${muted('Without these, web CLI dispatches can\'t show Claude Code output.')}`);
89
+ console.log(muted('Run `gipity status --repair-hooks` to re-install.'));
90
+ console.log(muted('Without these, web CLI dispatches can\'t show Claude Code output.'));
91
91
  }
92
92
  }
93
93
  });
@@ -1,5 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  import { sync } from '../sync.js';
3
+ import { createProgressReporter } from '../progress.js';
3
4
  import { error as clrError } from '../colors.js';
4
5
  export const syncCommand = new Command('sync')
5
6
  .description('Sync files')
@@ -8,7 +9,9 @@ export const syncCommand = new Command('sync')
8
9
  .option('--json', 'Output as JSON')
9
10
  .action(async (opts) => {
10
11
  try {
11
- const result = await sync({ plan: opts.plan, force: opts.force });
12
+ // JSON mode stays machine-clean; otherwise show live progress on a TTY.
13
+ const progress = opts.json ? undefined : createProgressReporter();
14
+ const result = await sync({ plan: opts.plan, force: opts.force, progress });
12
15
  if (opts.json) {
13
16
  console.log(JSON.stringify(result));
14
17
  }
@@ -86,7 +86,6 @@ export const testCommand = new Command('test')
86
86
  const config = requireConfig();
87
87
  await syncBeforeAction(opts);
88
88
  if (!opts.json) {
89
- console.log('');
90
89
  console.log(bold(`Running tests: ${filterPath || 'all'}`));
91
90
  console.log('');
92
91
  }
@@ -98,7 +97,7 @@ export const testCommand = new Command('test')
98
97
  });
99
98
  const runGuid = kickoff.data.runGuid;
100
99
  if (!opts.json) {
101
- console.log(` ${muted(`Run: ${runGuid}`)}`);
100
+ console.log(muted(`Run: ${runGuid}`));
102
101
  console.log('');
103
102
  }
104
103
  // Poll for results (streams results as they complete)
@@ -123,7 +122,6 @@ export const testCommand = new Command('test')
123
122
  if (data.skipped > 0)
124
123
  parts.push(muted(`${data.skipped} skipped`));
125
124
  console.log(`${parts.join(', ')} ${muted(`(${data.durationMs}ms)`)}`);
126
- console.log('');
127
125
  if (data.failed > 0)
128
126
  process.exit(1);
129
127
  }));
@@ -150,24 +148,22 @@ testCommand
150
148
  return;
151
149
  }
152
150
  const icon = data.status === 'running' ? muted('⧗') : data.status === 'passed' ? success('✓') : clrError('✗');
153
- console.log('');
154
- console.log(` ${icon} ${bold(data.status)} - ${data.passed}/${data.total} passed`);
151
+ console.log(`${icon} ${bold(data.status)} - ${data.passed}/${data.total} passed`);
155
152
  if (data.totalFiles > 0) {
156
- console.log(` ${muted(`Files: ${data.completedFiles}/${data.totalFiles}`)}`);
153
+ console.log(muted(`Files: ${data.completedFiles}/${data.totalFiles}`));
157
154
  }
158
155
  if (data.durationMs) {
159
- console.log(` ${muted(`Duration: ${data.durationMs}ms`)}`);
156
+ console.log(muted(`Duration: ${data.durationMs}ms`));
160
157
  }
161
158
  if (data.failed > 0) {
162
159
  console.log('');
163
160
  const failures = data.results.filter(r => r.status === 'failed');
164
161
  for (const f of failures) {
165
- console.log(` ${clrError('✗')} ${f.path}/${f.name}`);
162
+ console.log(`${clrError('✗')} ${f.path}/${f.name}`);
166
163
  if (f.error)
167
- console.log(` ${clrError(f.error)}`);
164
+ console.log(` ${clrError(f.error)}`);
168
165
  }
169
166
  }
170
- console.log('');
171
167
  }));
172
168
  // ── History subcommand ─────────────────────────────────────────────────
173
169
  testCommand
@@ -186,13 +182,11 @@ testCommand
186
182
  console.log(muted('No test runs yet.'));
187
183
  return;
188
184
  }
189
- console.log('');
190
185
  console.log(bold('Test History'));
191
186
  for (const entry of res.data) {
192
187
  const icon = entry.status === 'passed' ? success('✓') : entry.status === 'running' ? muted('⧗') : clrError('✗');
193
188
  const date = new Date(entry.started_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
194
- console.log(` ${icon} ${muted(date)} ${entry.passed}/${entry.total} passed ${muted(entry.run_guid)}`);
189
+ console.log(`${icon} ${muted(date)} ${entry.passed}/${entry.total} passed ${muted(entry.run_guid)}`);
195
190
  }
196
- console.log('');
197
191
  }));
198
192
  //# sourceMappingURL=test.js.map
@@ -19,7 +19,7 @@ function resolveInput(args, opts) {
19
19
  }
20
20
  function formatProfile(a) {
21
21
  const L = [];
22
- const row = (label, val) => L.push(` ${label.padEnd(22)}${val}`);
22
+ const row = (label, val) => L.push(`${label.padEnd(22)}${val}`);
23
23
  L.push(bold('Counts'));
24
24
  row('Characters', a.characters);
25
25
  row('Chars (no spaces)', a.charactersNoSpaces);
@@ -53,15 +53,15 @@ function formatProfile(a) {
53
53
  const freq = a.letterFrequency
54
54
  .map((f) => `${f.letter}:${f.count}`)
55
55
  .join(' ');
56
- L.push(` ${freq}`);
56
+ L.push(freq);
57
57
  }
58
58
  else {
59
- L.push(dim(' (no letters)'));
59
+ L.push(dim('(no letters)'));
60
60
  }
61
61
  if (a.wordFrequency.length) {
62
62
  L.push('');
63
63
  L.push(bold('Top words'));
64
- L.push(' ' + a.wordFrequency.map((w) => `${w.word}:${w.count}`).join(' '));
64
+ L.push(a.wordFrequency.map((w) => `${w.word}:${w.count}`).join(' '));
65
65
  }
66
66
  L.push('');
67
67
  L.push(bold('Other'));
@@ -99,7 +99,7 @@ const analyzeCommand = new Command('analyze')
99
99
  const counts = r.wholeWord
100
100
  ? `${r.nonOverlapping}`
101
101
  : `${r.nonOverlapping} non-overlapping, ${r.overlapping} overlapping`;
102
- console.log(` "${r.needle}" (${mode}): ${counts}`);
102
+ console.log(`"${r.needle}" (${mode}): ${counts}`);
103
103
  return;
104
104
  }
105
105
  if (opts.find !== undefined) {
@@ -107,29 +107,29 @@ const analyzeCommand = new Command('analyze')
107
107
  if (opts.json)
108
108
  return void console.log(JSON.stringify({ needle: opts.find, positions }));
109
109
  console.log(positions.length
110
- ? ` "${opts.find}" at: ${positions.join(', ')}`
111
- : dim(` "${opts.find}" not found`));
110
+ ? `"${opts.find}" at: ${positions.join(', ')}`
111
+ : dim(`"${opts.find}" not found`));
112
112
  return;
113
113
  }
114
114
  if (opts.anagram !== undefined) {
115
115
  const r = areAnagrams(text, opts.anagram);
116
116
  if (opts.json)
117
117
  return void console.log(JSON.stringify(r));
118
- console.log(` ${r.isAnagram ? 'yes' : 'no'} - anagram of "${opts.anagram}"`);
118
+ console.log(`${r.isAnagram ? 'yes' : 'no'} - anagram of "${opts.anagram}"`);
119
119
  return;
120
120
  }
121
121
  if (opts.word !== undefined) {
122
122
  const w = nthWord(text, opts.word);
123
123
  if (opts.json)
124
124
  return void console.log(JSON.stringify({ word: w, n: opts.word }));
125
- console.log(w === null ? dim(' (out of range)') : ` ${w}`);
125
+ console.log(w === null ? dim('(out of range)') : `${w}`);
126
126
  return;
127
127
  }
128
128
  if (opts.char !== undefined) {
129
129
  const c = nthChar(text, opts.char);
130
130
  if (opts.json)
131
131
  return void console.log(JSON.stringify({ char: c, n: opts.char }));
132
- console.log(c === null ? dim(' (out of range)') : ` ${c}`);
132
+ console.log(c === null ? dim('(out of range)') : `${c}`);
133
133
  return;
134
134
  }
135
135
  const analysis = analyzeText(text);
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * `gipity uninstall` - true reset. Stops the relay daemon, removes the
3
3
  * platform autostart service, revokes the device on the server (best-effort),
4
- * and wipes ~/.gipity/. Optionally wipes ~/GipityProjects/ on request.
4
+ * and wipes ~/.gipity/. Never touches ~/GipityProjects/ - your local
5
+ * project trees are yours to keep or remove yourself.
5
6
  *
6
7
  * Does not touch the npm-installed shim - the user removes that separately
7
8
  * via `npm uninstall -g gipity`.
@@ -91,76 +92,53 @@ async function revokeDeviceBestEffort() {
91
92
  export const uninstallCommand = new Command('uninstall')
92
93
  .description('Uninstall Gipity')
93
94
  .option('--yes', 'Skip confirmation prompts')
94
- .option('--purge-projects', 'Also delete ~/GipityProjects/ (your local project trees)')
95
95
  .action(async (opts) => {
96
96
  const autoYes = opts.yes || getAutoConfirm();
97
97
  const gipityDir = join(homedir(), '.gipity');
98
- const projectsDir = join(homedir(), 'GipityProjects');
98
+ console.log(`${bold('Gipity uninstall')} - this will:`);
99
+ console.log(`• Stop the running relay daemon (if any)`);
100
+ console.log(`• Remove the OS autostart service (launchd / systemd / Task Scheduler)`);
101
+ console.log(`• Revoke this device on the server (best-effort)`);
102
+ console.log(`• Delete ${gipityDir}/`);
99
103
  console.log('');
100
- console.log(` ${bold('Gipity uninstall')} - this will:`);
101
- console.log(` • Stop the running relay daemon (if any)`);
102
- console.log(` • Remove the OS autostart service (launchd / systemd / Task Scheduler)`);
103
- console.log(` • Revoke this device on the server (best-effort)`);
104
- console.log(` • Delete ${gipityDir}/`);
105
- console.log('');
106
- console.log(` ${dim('It will NOT remove the `gipity` binary. Run `npm uninstall -g gipity` afterward if you want that too.')}`);
104
+ console.log(`${dim('It will NOT remove the `gipity` binary. Run `npm uninstall -g gipity` afterward if you want that too.')}`);
107
105
  console.log('');
108
106
  if (!autoYes) {
109
- const ok = await confirm(' Proceed?');
107
+ const ok = await confirm('Proceed?');
110
108
  if (!ok) {
111
- console.log(` ${muted('Cancelled.')}`);
109
+ console.log(`${muted('Cancelled.')}`);
112
110
  return;
113
111
  }
114
112
  }
115
113
  // 1. Stop daemon.
116
114
  await stopDaemon();
117
- console.log(` ${success('Daemon stopped.')}`);
115
+ console.log(`${success('Daemon stopped.')}`);
118
116
  // 2. Remove OS service.
119
117
  const svc = removeServiceUnit();
120
118
  if (svc.ran && svc.ok)
121
- console.log(` ${success('Autostart service removed.')} ${svc.note ? dim(`(${svc.note})`) : ''}`);
119
+ console.log(`${success('Autostart service removed.')} ${svc.note ? dim(`(${svc.note})`) : ''}`);
122
120
  else if (svc.ran)
123
- console.log(` ${muted('Autostart service not installed or already gone.')}`);
121
+ console.log(`${muted('Autostart service not installed or already gone.')}`);
124
122
  else
125
- console.log(` ${muted(svc.note ?? 'Autostart skipped.')}`);
123
+ console.log(`${muted(svc.note ?? 'Autostart skipped.')}`);
126
124
  // 3. Revoke device on server.
127
125
  await revokeDeviceBestEffort();
128
- console.log(` ${success('Device revoked on server (or was already revoked).')}`);
126
+ console.log(`${success('Device revoked on server (or was already revoked).')}`);
129
127
  // 4. Wipe ~/.gipity/.
130
128
  if (existsSync(gipityDir)) {
131
129
  try {
132
130
  rmSync(gipityDir, { recursive: true, force: true });
133
- console.log(` ${success(`Removed ${gipityDir}/`)}`);
131
+ console.log(`${success(`Removed ${gipityDir}/`)}`);
134
132
  }
135
133
  catch (err) {
136
- console.error(` ${clrError(`Could not remove ${gipityDir}: ${err?.message || err}`)}`);
134
+ console.error(`${clrError(`Could not remove ${gipityDir}: ${err?.message || err}`)}`);
137
135
  }
138
136
  }
139
137
  else {
140
- console.log(` ${muted(`${gipityDir}/ already gone.`)}`);
138
+ console.log(`${muted(`${gipityDir}/ already gone.`)}`);
141
139
  }
142
- // 5. Offer to wipe ~/GipityProjects/.
143
- if (existsSync(projectsDir)) {
144
- let alsoPurge = opts.purgeProjects === true;
145
- if (!alsoPurge && !autoYes) {
146
- alsoPurge = await confirm(` Also delete ${projectsDir}/ (your local project trees)?`);
147
- }
148
- if (alsoPurge) {
149
- try {
150
- rmSync(projectsDir, { recursive: true, force: true });
151
- console.log(` ${success(`Removed ${projectsDir}/`)}`);
152
- }
153
- catch (err) {
154
- console.error(` ${clrError(`Could not remove ${projectsDir}: ${err?.message || err}`)}`);
155
- }
156
- }
157
- else {
158
- console.log(` ${muted(`Kept ${projectsDir}/ (projects still live in the cloud).`)}`);
159
- }
160
- }
161
- console.log('');
162
- console.log(` ${success('Uninstall complete.')} ${dim('Run')} ${brand('npm uninstall -g gipity')} ${dim('to remove the binary too.')}`);
163
- console.log(` ${dim('Then run')} ${brand('hash -r')} ${dim('(or open a new shell) so bash forgets the old binary path.')}`);
164
140
  console.log('');
141
+ console.log(`${success('Uninstall complete.')} ${dim('Run')} ${brand('npm uninstall -g gipity')} ${dim('to remove the binary too.')}`);
142
+ console.log(`${dim('Then run')} ${brand('hash -r')} ${dim('(or open a new shell) so bash forgets the old binary path.')}`);
165
143
  });
166
144
  //# sourceMappingURL=uninstall.js.map
@@ -4,7 +4,6 @@ import { success, warning, info, dim } from '../colors.js';
4
4
  export const updateCommand = new Command('update')
5
5
  .description('Update the CLI')
6
6
  .action(async () => {
7
- console.log('');
8
7
  console.log(info('Checking for updates...'));
9
8
  const result = await runCheck({ force: true, verbose: true });
10
9
  console.log('');
@@ -18,6 +17,5 @@ export const updateCommand = new Command('update')
18
17
  else {
19
18
  console.log(warning(`No update applied: ${result.reason}`));
20
19
  }
21
- console.log('');
22
20
  });
23
21
  //# sourceMappingURL=update.js.map