gipity 1.0.356 → 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 (60) hide show
  1. package/dist/banner.js +3 -1
  2. package/dist/capture/sources/claude-code.js +76 -11
  3. package/dist/commands/add.js +45 -28
  4. package/dist/commands/agent.js +3 -5
  5. package/dist/commands/approval.js +3 -3
  6. package/dist/commands/audit.js +2 -2
  7. package/dist/commands/chat.js +4 -4
  8. package/dist/commands/claude.js +8 -9
  9. package/dist/commands/credits.js +1 -1
  10. package/dist/commands/db.js +7 -6
  11. package/dist/commands/deploy.js +5 -8
  12. package/dist/commands/doctor.js +11 -13
  13. package/dist/commands/domain.js +18 -15
  14. package/dist/commands/email.js +0 -4
  15. package/dist/commands/fn.js +2 -2
  16. package/dist/commands/generate.js +73 -18
  17. package/dist/commands/job.js +6 -6
  18. package/dist/commands/location.js +7 -7
  19. package/dist/commands/login.js +2 -16
  20. package/dist/commands/logout.js +2 -3
  21. package/dist/commands/logs.js +1 -1
  22. package/dist/commands/page-eval.js +48 -7
  23. package/dist/commands/page-fetch.js +136 -0
  24. package/dist/commands/page-inspect.js +59 -29
  25. package/dist/commands/page-screenshot.js +51 -41
  26. package/dist/commands/page-test.js +86 -0
  27. package/dist/commands/page.js +8 -3
  28. package/dist/commands/plan.js +4 -4
  29. package/dist/commands/push.js +2 -6
  30. package/dist/commands/realtime.js +7 -9
  31. package/dist/commands/relay-install.js +18 -21
  32. package/dist/commands/relay.js +29 -31
  33. package/dist/commands/sandbox.js +16 -3
  34. package/dist/commands/service.js +54 -0
  35. package/dist/commands/skill.js +2 -1
  36. package/dist/commands/status.js +2 -2
  37. package/dist/commands/sync.js +4 -1
  38. package/dist/commands/test.js +7 -13
  39. package/dist/commands/text.js +148 -0
  40. package/dist/commands/uninstall.js +20 -42
  41. package/dist/commands/update.js +0 -2
  42. package/dist/commands/upload.js +4 -4
  43. package/dist/commands/workflow.js +11 -16
  44. package/dist/config.js +8 -1
  45. package/dist/help-skills.js +1 -0
  46. package/dist/helpers/output.js +52 -8
  47. package/dist/helpers/text-analysis.js +200 -0
  48. package/dist/hooks/capture-runner.js +32 -8
  49. package/dist/index.js +35 -2
  50. package/dist/knowledge.js +32 -7
  51. package/dist/progress.js +60 -0
  52. package/dist/project-setup.js +5 -1
  53. package/dist/provider-docs.js +7 -7
  54. package/dist/relay/daemon.js +11 -1
  55. package/dist/relay/stream-json.js +45 -8
  56. package/dist/setup.js +38 -11
  57. package/dist/sync.js +30 -8
  58. package/dist/updater/shim.js +18 -4
  59. package/dist/upload.js +6 -0
  60. package/package.json +5 -4
package/dist/banner.js CHANGED
@@ -191,7 +191,9 @@ function renderBox(opts, outerW, bodyLines) {
191
191
  for (const row of bodyLines)
192
192
  lines.push(row);
193
193
  lines.push(border('╰') + border('─'.repeat(innerW)) + border('╯'));
194
- console.log('\n' + lines.join('\n') + '\n');
194
+ // Leading blank comes from the central output frame; keep the trailing one
195
+ // (it separates the banner from the picker/output that follows).
196
+ console.log(lines.join('\n') + '\n');
195
197
  }
196
198
  // ── Narrow layout (single panel, egg only) ────────────────────────────
197
199
  function printNarrow(opts, outerW) {
@@ -24,6 +24,25 @@
24
24
  * so retried POSTs are deduplicated by the partial unique index on
25
25
  * messages(conversation_id, source_uuid).
26
26
  */
27
+ /** Pull token usage + model + stop_reason off an assistant `message` object.
28
+ * Same shape in the transcript JSONL and the stream-json assistant event, so
29
+ * both capture paths share this. Only includes keys that are actually present
30
+ * (so they spread cleanly onto an assistant entry without writing nulls).
31
+ * Cost is NOT here — it doesn't exist per-message; only the stream `result`
32
+ * footer reports a session total. */
33
+ export function usageFields(msg) {
34
+ const out = {};
35
+ const u = msg?.usage;
36
+ if (u && typeof u.input_tokens === 'number')
37
+ out.input_tokens = u.input_tokens;
38
+ if (u && typeof u.output_tokens === 'number')
39
+ out.output_tokens = u.output_tokens;
40
+ if (typeof msg?.model === 'string')
41
+ out.model = msg.model;
42
+ if (typeof msg?.stop_reason === 'string')
43
+ out.stop_reason = msg.stop_reason;
44
+ return out;
45
+ }
27
46
  /** Extract joined `{type:'text', text}` blocks into a single string.
28
47
  * The full blocks array is emitted separately so the client can render
29
48
  * text + tool_use in the agent's original narrative order. */
@@ -37,8 +56,38 @@ function joinText(content) {
37
56
  }
38
57
  return parts.join('\n');
39
58
  }
40
- /** Map a single parsed transcript line to zero or more ingest entries. */
41
- export function transcriptLineToEntries(line) {
59
+ /** Stamp a unique `source_uuid` on every entry parsed from one transcript line.
60
+ *
61
+ * One transcript line yields many entries (an assistant line emits its text
62
+ * entry PLUS one tool_use entry per tool call). The server dedupes ingest on
63
+ * the partial unique index messages(conversation_id, source_uuid) with
64
+ * ON CONFLICT DO NOTHING. If all siblings shared the bare `line.uuid`, the
65
+ * first entry (the assistant text) would claim the row and every subsequent
66
+ * tool_use would be silently dropped - leaving the later tool_result to land
67
+ * as a name-less stub. That bug made 100% of terminal-session tool calls lose
68
+ * their tool_name.
69
+ *
70
+ * The primary entry keeps the bare line uuid (so it still dedupes against rows
71
+ * written before this fix); each sibling gets a deterministic `#N` suffix.
72
+ * Determinism matters: a retried POST re-parses the same line in the same
73
+ * order, producing identical suffixes, so dedup still collapses retries. */
74
+ function stampEntries(entries, lineUuid, lineTs) {
75
+ return entries.map((e, i) => ({
76
+ ...e,
77
+ source_uuid: i === 0 ? lineUuid : `${lineUuid}#${i}`,
78
+ ...(lineTs ? { ts: lineTs } : {}),
79
+ }));
80
+ }
81
+ /** Map a single parsed transcript line to zero or more ingest entries.
82
+ *
83
+ * `toolNames` (optional) is a per-transcript `tool_use_id → tool_name`
84
+ * map threaded across lines by `parseTranscript`: an assistant line
85
+ * records each tool call's name into it, and the later user line that
86
+ * carries the paired `tool_result` reads the name back out. This lets
87
+ * the server denormalize `tool_name` onto the tool row even when the
88
+ * result lands as a stub (the tool_use row missing/deduped). The
89
+ * `tool_result` block itself never carries the name. */
90
+ export function transcriptLineToEntries(line, toolNames) {
42
91
  if (!line || typeof line !== 'object')
43
92
  return [];
44
93
  if (typeof line.type !== 'string')
@@ -54,13 +103,14 @@ export function transcriptLineToEntries(line) {
54
103
  if (line.toolUseResult && !line.message)
55
104
  return [];
56
105
  const srcUuid = line.uuid;
106
+ const lineTs = typeof line.timestamp === 'string' ? line.timestamp : undefined;
57
107
  const msg = line.message ?? {};
58
108
  if (line.type === 'user') {
59
109
  const content = msg.content;
60
110
  if (typeof content === 'string') {
61
111
  if (!content)
62
112
  return [];
63
- return [{ kind: 'prompt', prompt: content, source_uuid: srcUuid }];
113
+ return stampEntries([{ kind: 'prompt', prompt: content }], srcUuid, lineTs);
64
114
  }
65
115
  if (Array.isArray(content)) {
66
116
  const out = [];
@@ -69,18 +119,18 @@ export function transcriptLineToEntries(line) {
69
119
  out.push({
70
120
  kind: 'tool_result',
71
121
  tool_use_id: b.tool_use_id,
122
+ tool_name: toolNames?.get(b.tool_use_id),
72
123
  content: b.content ?? null,
73
124
  is_error: Boolean(b.is_error),
74
- source_uuid: srcUuid,
75
125
  });
76
126
  }
77
127
  else if (b?.type === 'text' && typeof b.text === 'string' && b.text) {
78
128
  // A user message with raw text blocks (rare - first-turn preamble
79
129
  // is sometimes split this way). Treat like a prompt.
80
- out.push({ kind: 'prompt', prompt: b.text, source_uuid: srcUuid });
130
+ out.push({ kind: 'prompt', prompt: b.text });
81
131
  }
82
132
  }
83
- return out;
133
+ return stampEntries(out, srcUuid, lineTs);
84
134
  }
85
135
  return [];
86
136
  }
@@ -89,20 +139,21 @@ export function transcriptLineToEntries(line) {
89
139
  const text = joinText(content);
90
140
  const out = [];
91
141
  if (text || content.length) {
92
- out.push({ kind: 'assistant', text, blocks: content, source_uuid: srcUuid });
142
+ out.push({ kind: 'assistant', text, blocks: content, ...usageFields(msg) });
93
143
  }
94
144
  for (const block of content) {
95
145
  if (block?.type === 'tool_use' && typeof block.id === 'string') {
146
+ const toolName = typeof block.name === 'string' ? block.name : 'tool';
147
+ toolNames?.set(block.id, toolName);
96
148
  out.push({
97
149
  kind: 'tool_use',
98
150
  tool_use_id: block.id,
99
- tool_name: typeof block.name === 'string' ? block.name : 'tool',
151
+ tool_name: toolName,
100
152
  tool_input: block.input ?? null,
101
- source_uuid: srcUuid,
102
153
  });
103
154
  }
104
155
  }
105
- return out;
156
+ return stampEntries(out, srcUuid, lineTs);
106
157
  }
107
158
  // Other envelope types (system notes, hook-emitted, …) - not captured.
108
159
  return [];
@@ -120,6 +171,11 @@ export function parseTranscript(content, afterUuid) {
120
171
  let seenWatermark = afterUuid === null;
121
172
  let lastUuid = afterUuid;
122
173
  const out = [];
174
+ // tool_use_id → tool_name, accumulated across lines so a later
175
+ // tool_result can be denormalized with its tool's name. Built from the
176
+ // whole file (including pre-watermark lines) so a result whose tool_use
177
+ // was forwarded in an earlier sweep still resolves its name.
178
+ const toolNames = new Map();
123
179
  for (const raw of content.split('\n')) {
124
180
  const line = raw.trim();
125
181
  if (!line)
@@ -131,12 +187,21 @@ export function parseTranscript(content, afterUuid) {
131
187
  catch {
132
188
  continue;
133
189
  }
190
+ // Record tool names from every assistant line we scan, even before the
191
+ // watermark - the map is read-only side state, it doesn't emit entries.
134
192
  if (!seenWatermark) {
193
+ if (Array.isArray(parsed?.message?.content)) {
194
+ for (const block of parsed.message.content) {
195
+ if (block?.type === 'tool_use' && typeof block.id === 'string') {
196
+ toolNames.set(block.id, typeof block.name === 'string' ? block.name : 'tool');
197
+ }
198
+ }
199
+ }
135
200
  if (parsed?.uuid === afterUuid)
136
201
  seenWatermark = true;
137
202
  continue;
138
203
  }
139
- const entries = transcriptLineToEntries(parsed);
204
+ const entries = transcriptLineToEntries(parsed, toolNames);
140
205
  if (entries.length) {
141
206
  for (const e of entries)
142
207
  out.push(e);
@@ -7,35 +7,38 @@ import { requireConfig } from '../config.js';
7
7
  import { sync } from '../sync.js';
8
8
  import { success, muted, bold } from '../colors.js';
9
9
  import { run } from '../helpers/index.js';
10
- // Catalog mirrored from platform/packages/shared (TEMPLATES + KITS) -
11
- // the CLI ships as a standalone npm package and can't depend on the private
12
- // shared workspace. Keep these lists in sync when catalog entries change.
13
- //
14
- // Templates install a whole app (blank wiring or a working starter demo).
15
- // Kits are reusable building blocks added into an existing app's src/packages/.
16
- const TEMPLATES = ['web-simple', '3d-engine'];
17
- const STARTERS = ['web-fullstack', 'web-vision-cam', '2d-game', '3d-world', 'api'];
10
+ const STARTERS = [
11
+ { key: 'web-fullstack', hint: 'backend API + database (weather-by-zip demo)' },
12
+ { key: 'web-vision-cam', hint: 'fullscreen camera app with on-device vision (MediaPipe)' },
13
+ { key: '2d-game', hint: '2D games with Phaser 3 - platformer, arcade, puzzle' },
14
+ { key: '3d-world', hint: 'playable 3D multiplayer rocket-launcher demo' },
15
+ { key: 'api', hint: 'pure API backend, no frontend' },
16
+ ];
17
+ const BLANK = [
18
+ { key: 'web-simple', hint: 'static frontend-only site - pages, dashboards, simple games' },
19
+ { key: '3d-engine', hint: '3D multiplayer wiring - Three.js + Rapier + Gipity Realtime' },
20
+ ];
18
21
  const HIDDEN = [{ key: 'app-itsm', hint: 'IT service management / helpdesk / ticketing' }];
19
22
  const KITS = [
20
23
  { key: 'realtime', hint: 'multiplayer / presence / shared state' },
21
24
  { key: 'web-vision-mediapipe', hint: 'browser camera vision - gesture, pose, object detection' },
25
+ { key: 'i18n', hint: 'multi-language web apps - language picker, RTL, translations' },
22
26
  ];
23
- function printCatalog() {
24
- console.log('');
25
- console.log(`${bold('Templates')} ${muted('- install a whole app into an empty project')}`);
26
- console.log(` ${TEMPLATES.join(', ')} ${muted('(blank wiring)')}`);
27
- console.log(` ${STARTERS.join(', ')} ${muted('(working demos)')}`);
28
- console.log('');
29
- console.log(`${bold('Kits')} ${muted('- add a reusable building block into an existing app')}`);
30
- for (const k of KITS)
31
- console.log(` ${k.key} ${muted('- ' + k.hint)}`);
32
- console.log('');
33
- console.log(`${bold('Local path')} ${muted('- install from a directory on disk (template or kit)')}`);
34
- console.log(` ${muted('gipity add ./path/to/template (or ~/path, /abs/path)')}`);
35
- console.log(` ${muted('gipity add ./path/to/kit (auto-detected via package.json gipity.install)')}`);
36
- console.log('');
37
- console.log(muted('Usage: gipity add <name|path> [--title <t>] [--description <d>] [--force]'));
38
- console.log('');
27
+ // The catalog block, rendered once and reused by the full help output
28
+ // (`gipity add` / `gipity add --help`) and the bare listing (`gipity add
29
+ // --list`) so they can never drift. Three sections, one entry per line, keys
30
+ // column-aligned. No leading/trailing blank lines - callers add surrounding
31
+ // whitespace.
32
+ function catalogText() {
33
+ const width = Math.max(...[...STARTERS, ...BLANK, ...KITS].map(e => e.key.length));
34
+ const row = (e) => ` ${e.key.padEnd(width)} ${muted(e.hint)}`;
35
+ const section = (title, blurb, entries) => [`${bold(title)} ${muted('- ' + blurb)}`, ...entries.map(row)].join('\n');
36
+ return [
37
+ 'Names to pass to `gipity add <name>`:',
38
+ section('Templates (working demos)', 'complete apps to run, then extend or replace', STARTERS),
39
+ section('Templates (blank wiring)', 'minimal framework setup - build your app on top', BLANK),
40
+ section('Kits', 'building blocks to add into an app you already scaffolded', KITS),
41
+ ].join('\n\n');
39
42
  }
40
43
  // ─── Local-path payload mode ────────────────────────────────────────────────
41
44
  //
@@ -136,14 +139,28 @@ function buildLocalPayload(rootDir) {
136
139
  }
137
140
  export const addCommand = new Command('add')
138
141
  .description('Add a template (scaffold an app) or a kit (reusable building block) to the project. Pass ./path/to/dir to install a local template directly.')
139
- .argument('[name]', 'Template/kit key, OR a local directory path (./, ~/, or /abs). Omit to list the catalog.')
142
+ .argument('[name]', 'Template/kit key, OR a local directory path (./, ~/, or /abs). Omit for help; use --list for just the catalog.')
140
143
  .option('--title <title>', 'App title - templates only (defaults to project name)')
141
144
  .option('--description <desc>', 'App description for meta tags - templates only')
142
145
  .option('--force', 'Templates only: overwrite any colliding files')
146
+ .option('--list', 'List the template/kit catalog and exit')
143
147
  .option('--json', 'Output as JSON')
144
- .action((name, opts) => run('Add', async () => {
148
+ .addHelpText('after', () => catalogText() + '\n\n'
149
+ + muted('Local path gipity add ./dir (or ~/path, /abs) - template or kit, auto-detected'))
150
+ .action((name, opts, command) => run('Add', async () => {
151
+ // `--list` is a bare catalog dump; no project/config needed.
152
+ if (opts.list) {
153
+ if (opts.json) {
154
+ console.log(JSON.stringify({ templates: { starters: STARTERS, blank: BLANK }, kits: KITS }));
155
+ }
156
+ else {
157
+ console.log(catalogText());
158
+ }
159
+ return;
160
+ }
161
+ // No name = show the full help (usage + options + catalog), same as --help.
145
162
  if (!name) {
146
- printCatalog();
163
+ command.outputHelp();
147
164
  return;
148
165
  }
149
166
  const config = requireConfig();
@@ -190,7 +207,7 @@ export const addCommand = new Command('add')
190
207
  console.log(success(`Scaffolded "${data.title}" (${data.type}) - ${data.files.length} files:`));
191
208
  }
192
209
  for (const f of data.files)
193
- console.log(` ${f}`);
210
+ console.log(`${f}`);
194
211
  if (data.notes?.length) {
195
212
  console.log('');
196
213
  for (const n of data.notes)
@@ -1,7 +1,7 @@
1
1
  import { Command } from 'commander';
2
2
  import { get, post, put, del } from '../api.js';
3
3
  import { requireConfig, saveConfig } from '../config.js';
4
- import { error as clrError } from '../colors.js';
4
+ import { error as clrError, success } from '../colors.js';
5
5
  import { run, printList, printResult } from '../helpers/index.js';
6
6
  import { confirm } from '../utils.js';
7
7
  export const agentCommand = new Command('agent')
@@ -51,11 +51,9 @@ agentCommand
51
51
  console.log(JSON.stringify(res.data));
52
52
  }
53
53
  else {
54
- console.log('');
55
- console.log(`Created "${res.data.name}" (${res.data.short_guid})`);
54
+ console.log(success(`Created "${res.data.name}" (${res.data.short_guid})`));
56
55
  if (opts.switch)
57
56
  console.log('Switched.');
58
- console.log('');
59
57
  }
60
58
  }));
61
59
  agentCommand
@@ -114,7 +112,7 @@ agentCommand
114
112
  const res = await get('/agents');
115
113
  const match = res.data.find(a => a.name === name || a.short_guid === name);
116
114
  if (!match) {
117
- console.error(`Agent "${name}" not found.`);
115
+ console.error(clrError(`Agent "${name}" not found.`));
118
116
  process.exit(1);
119
117
  }
120
118
  if (!await confirm(`Delete agent "${match.name}"?`)) {
@@ -1,6 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  import { get, post } from '../api.js';
3
- import { muted } from '../colors.js';
3
+ import { muted, success } from '../colors.js';
4
4
  import { run, printList, printResult } from '../helpers/index.js';
5
5
  function timeAgo(dateStr) {
6
6
  const diffMs = Date.now() - new Date(dateStr).getTime();
@@ -53,7 +53,7 @@ approvalCommand
53
53
  if (opts.expiresIn)
54
54
  body.expires_in_minutes = Number(opts.expiresIn);
55
55
  const res = await post('/approvals', body);
56
- printResult(`Created ${res.data.guid}.`, opts, res.data);
56
+ printResult(success(`Created ${res.data.guid}.`), opts, res.data);
57
57
  }));
58
58
  approvalCommand
59
59
  .command('answer <guid> [selection...]')
@@ -112,6 +112,6 @@ approvalCommand
112
112
  throw new Error('Expected approval guid like ap_xxxxxxxx');
113
113
  }
114
114
  await post(`/approvals/${guid}/cancel`, {});
115
- printResult(`Cancelled ${guid}.`, opts, { guid, cancelled: true });
115
+ printResult(success(`Cancelled ${guid}.`), opts, { guid, cancelled: true });
116
116
  }));
117
117
  //# sourceMappingURL=approval.js.map
@@ -1,7 +1,7 @@
1
1
  import { Command } from 'commander';
2
2
  import { get } from '../api.js';
3
3
  import { requireConfig } from '../config.js';
4
- import { muted } from '../colors.js';
4
+ import { muted, brand } from '../colors.js';
5
5
  import { run, printList } from '../helpers/index.js';
6
6
  export const auditCommand = new Command('audit')
7
7
  .description('Query audit logs');
@@ -47,6 +47,6 @@ auditCommand
47
47
  if (opts.entity)
48
48
  params.set('entity_type', opts.entity);
49
49
  const res = await get(`/projects/${config.projectGuid}/audit/count?${params}`);
50
- console.log(opts.json ? JSON.stringify(res.data) : `${res.data.count} events`);
50
+ console.log(opts.json ? JSON.stringify(res.data) : `${brand(String(res.data.count))} events`);
51
51
  }));
52
52
  //# sourceMappingURL=audit.js.map
@@ -2,7 +2,7 @@ import { Command } from 'commander';
2
2
  import { get, post, put, del } from '../api.js';
3
3
  import { resolveProjectContext, saveConfig } from '../config.js';
4
4
  import { sync } from '../sync.js';
5
- import { error as clrError, muted } from '../colors.js';
5
+ import { error as clrError, muted, success } from '../colors.js';
6
6
  import { run, printList, printResult } from '../helpers/index.js';
7
7
  export const chatCommand = new Command('chat')
8
8
  .description('Send a message to your agent')
@@ -95,7 +95,7 @@ chatCommand
95
95
  .action((guid, titleParts, opts) => run('Rename', async () => {
96
96
  const title = titleParts.join(' ');
97
97
  await put(`/conversations/${guid}`, { title });
98
- printResult(`Renamed ${guid} → "${title}".`, opts, { guid, title });
98
+ printResult(success(`Renamed ${guid} → "${title}".`), opts, { guid, title });
99
99
  }));
100
100
  chatCommand
101
101
  .command('archive <guid>')
@@ -103,7 +103,7 @@ chatCommand
103
103
  .option('--json', 'Output as JSON')
104
104
  .action((guid, opts) => run('Archive', async () => {
105
105
  await put(`/conversations/${guid}`, { archive: true });
106
- printResult(`Archived ${guid}.`, opts, { guid, archived: true });
106
+ printResult(success(`Archived ${guid}.`), opts, { guid, archived: true });
107
107
  }));
108
108
  chatCommand
109
109
  .command('delete <guid>')
@@ -111,6 +111,6 @@ chatCommand
111
111
  .option('--json', 'Output as JSON')
112
112
  .action((guid, opts) => run('Delete', async () => {
113
113
  await del(`/conversations/${guid}`);
114
- printResult(`Deleted ${guid}.`, opts, { guid, deleted: true });
114
+ printResult(success(`Deleted ${guid}.`), opts, { guid, deleted: true });
115
115
  }));
116
116
  //# sourceMappingURL=chat.js.map
@@ -27,6 +27,7 @@ import * as relayState from '../relay/state.js';
27
27
  import { maybeOfferRelayOn, ensureDaemonRunning } from '../relay/onboarding.js';
28
28
  import { prompt, promptBoxed, pickOne, decodeJwtExp, confirm } from '../utils.js';
29
29
  import { brand, bold, info, success, error as clrError, muted } from '../colors.js';
30
+ import { createProgressReporter } from '../progress.js';
30
31
  import { printBanner } from '../banner.js';
31
32
  import { scanForAdoption, isLikelyEmpty, canAdoptCwd, formatCwdLabel, formatBytes, adoptCurrentDir, ADOPT_THRESHOLDS, } from '../adopt-cwd.js';
32
33
  const __clDir = dirname(fileURLToPath(import.meta.url));
@@ -297,12 +298,10 @@ export const claudeCommand = new Command('claude')
297
298
  // ── Step 2: Project ───────────────────────────────────────────────
298
299
  let initialPrompt = '';
299
300
  let headlessNewProject = false;
300
- // Single status line for the (otherwise silent) sync phases, so a long
301
- // sync of a large tree no longer reads as a hang.
302
- const syncProgress = (msg) => {
303
- if (!nonInteractive)
304
- console.log(` ${muted(msg)}`);
305
- };
301
+ // One reporter for the (otherwise silent) sync phases + upload bar, so a
302
+ // long sync of a large tree no longer reads as a hang. Stays silent in
303
+ // headless (-p) runs to keep machine-readable output clean.
304
+ const syncProgress = nonInteractive ? undefined : createProgressReporter();
306
305
  let existing = getConfig();
307
306
  let forceAdoptCwd = false;
308
307
  // Ambiguity guard: a `.gipity.json` inherited from an ANCESTOR directory
@@ -394,7 +393,7 @@ export const claudeCommand = new Command('claude')
394
393
  });
395
394
  console.log(`\n Using ${projectDir}`);
396
395
  try {
397
- const result = await sync({ interactive: !nonInteractive, onProgress: syncProgress });
396
+ const result = await sync({ interactive: !nonInteractive, progress: syncProgress });
398
397
  if (result.applied > 0) {
399
398
  console.log(` Synced ${result.applied} change${result.applied > 1 ? 's' : ''} with Gipity.`);
400
399
  }
@@ -462,7 +461,7 @@ export const claudeCommand = new Command('claude')
462
461
  }
463
462
  if (doSync) {
464
463
  try {
465
- const result = await sync({ interactive: !nonInteractive, onProgress: syncProgress });
464
+ const result = await sync({ interactive: !nonInteractive, progress: syncProgress });
466
465
  if (result.applied > 0) {
467
466
  console.log(` Synced ${result.applied} change${result.applied > 1 ? 's' : ''} with Gipity.`);
468
467
  }
@@ -588,7 +587,7 @@ export const claudeCommand = new Command('claude')
588
587
  console.log(`\n Using ${projectDir}`);
589
588
  // Unified sync - push and pull resolved via three-way merge (non-fatal)
590
589
  try {
591
- const result = await sync({ interactive: !nonInteractive, onProgress: syncProgress });
590
+ const result = await sync({ interactive: !nonInteractive, progress: syncProgress });
592
591
  if (result.applied > 0) {
593
592
  console.log(` Synced ${result.applied} change${result.applied > 1 ? 's' : ''} with Gipity.`);
594
593
  }
@@ -28,7 +28,7 @@ export const creditsCommand = new Command('credits')
28
28
  if (res.data.balances.length > 0) {
29
29
  for (const b of res.data.balances) {
30
30
  const exp = new Date(b.expiresAt).toLocaleDateString();
31
- console.log(` ${b.source}: ${b.creditsRemaining.toLocaleString()} ${muted(`expires ${exp}`)}`);
31
+ console.log(`${b.source}: ${b.creditsRemaining.toLocaleString()} ${muted(`expires ${exp}`)}`);
32
32
  }
33
33
  }
34
34
  }
@@ -1,7 +1,7 @@
1
1
  import { Command } from 'commander';
2
2
  import { get, post, sendMessage } from '../api.js';
3
3
  import { requireConfig } from '../config.js';
4
- import { error as clrError } from '../colors.js';
4
+ import { error as clrError, success } from '../colors.js';
5
5
  import { run, printList } from '../helpers/index.js';
6
6
  import { confirm } from '../utils.js';
7
7
  export const dbCommand = new Command('db')
@@ -62,12 +62,10 @@ dbCommand
62
62
  console.log(JSON.stringify(res.data));
63
63
  return;
64
64
  }
65
- console.log('');
66
65
  console.log(`Databases: ${count}/${limit}`);
67
66
  console.log('');
68
67
  if (databases.length === 0) {
69
68
  console.log('No databases.');
70
- console.log('');
71
69
  return;
72
70
  }
73
71
  // Group by project
@@ -78,13 +76,16 @@ dbCommand
78
76
  grouped.set(key, []);
79
77
  grouped.get(key).push(db);
80
78
  }
79
+ let first = true;
81
80
  for (const [projectGuid, dbs] of grouped) {
81
+ if (!first)
82
+ console.log('');
83
+ first = false;
82
84
  const label = dbs[0].projectSlug || dbs[0].projectName || projectGuid;
83
85
  console.log(label);
84
86
  for (const db of dbs) {
85
- console.log(` ${db.friendlyName}`);
87
+ console.log(`${db.friendlyName}`);
86
88
  }
87
- console.log();
88
89
  }
89
90
  }
90
91
  else {
@@ -143,7 +144,7 @@ dbCommand
143
144
  console.log(JSON.stringify({ success: true }));
144
145
  }
145
146
  else {
146
- console.log(`Dropped database '${name}'.`);
147
+ console.log(success(`Dropped database '${name}'.`));
147
148
  }
148
149
  }));
149
150
  //# sourceMappingURL=db.js.map
@@ -31,8 +31,6 @@ export const deployCommand = new Command('deploy')
31
31
  }
32
32
  const config = requireConfig();
33
33
  await syncBeforeAction(opts);
34
- if (!opts.json)
35
- console.log('');
36
34
  // Call server - pipeline runs entirely server-side
37
35
  const res = await post(`/projects/${config.projectGuid}/deploy`, {
38
36
  target,
@@ -52,26 +50,26 @@ export const deployCommand = new Command('deploy')
52
50
  console.log(muted('─'.repeat(40)));
53
51
  if (d.phases && d.phases.length > 0) {
54
52
  for (const phase of d.phases) {
55
- console.log(` ${statusIcon(phase.status)} ${bold(phase.name)}: ${phase.summary}`);
53
+ console.log(`${statusIcon(phase.status)} ${bold(phase.name)}: ${phase.summary}`);
56
54
  }
57
55
  }
58
56
  else {
59
57
  // Fallback for simple deploys without phases
60
58
  const size = formatSize(d.totalBytes);
61
- console.log(` ${success('✓')} ${d.fileCount} files (${size}) → ${success(d.url)}`);
59
+ console.log(`${success('✓')} ${d.fileCount} files (${size}) → ${success(d.url)}`);
62
60
  }
63
61
  if (d.customDomains?.length) {
64
- console.log(` ${muted('Also:')} ${d.customDomains.join(', ')}`);
62
+ console.log(`${muted('Also:')} ${d.customDomains.join(', ')}`);
65
63
  }
66
64
  if (d.warning) {
67
- console.log(` ${warning(d.warning)}`);
65
+ console.log(`${warning(d.warning)}`);
68
66
  }
69
67
  // Show example curl commands for public endpoints
70
68
  if (d.examples && d.examples.length > 0) {
71
69
  console.log('');
72
70
  console.log(bold('Test your endpoints:'));
73
71
  for (const ex of d.examples) {
74
- console.log(` ${muted(ex)}`);
72
+ console.log(`${muted(ex)}`);
75
73
  }
76
74
  }
77
75
  console.log(muted('─'.repeat(40)));
@@ -83,6 +81,5 @@ export const deployCommand = new Command('deploy')
83
81
  else {
84
82
  console.log(success(`✓ Deployed to ${target}`) + muted(` (${d.elapsedMs}ms)`));
85
83
  }
86
- console.log('');
87
84
  }));
88
85
  //# sourceMappingURL=deploy.js.map
@@ -46,22 +46,20 @@ export const doctorCommand = new Command('doctor')
46
46
  const dis = updatesDisabled();
47
47
  const local = localVersion();
48
48
  const localOk = existsSync(LOCAL_ENTRY);
49
- console.log('');
50
49
  console.log(bold('Gipity CLI - doctor'));
51
50
  console.log('');
52
- console.log(` ${muted('shim version ')} ${shimVersion()}`);
53
- console.log(` ${muted('local version ')} ${local ?? dim('not installed')} ${localOk ? success('✓') : warning('(running from shim fallback)')}`);
54
- console.log(` ${muted('local install ')} ${LOCAL_PKG_DIR}`);
55
- console.log('');
56
- console.log(` ${muted('auto-updates ')} ${dis.disabled ? warning(`disabled (${dis.reason})`) : success('enabled')}`);
57
- console.log(` ${muted('settings file ')} ${existsSync(SETTINGS_FILE) ? SETTINGS_FILE : dim('(default)')} autoUpdates=${settings.autoUpdates}`);
58
- console.log(` ${muted('last check ')} ${rel(state.lastCheckAt)}`);
59
- console.log(` ${muted('last error ')} ${state.lastError ? clrError(state.lastError) : dim('none')}`);
60
- console.log(` ${muted('state file ')} ${existsSync(STATE_FILE) ? STATE_FILE : dim('(none yet)')}`);
61
- console.log(` ${muted('update log ')} ${existsSync(UPDATE_LOG) ? `${UPDATE_LOG} (${statSync(UPDATE_LOG).size} bytes)` : dim('(none yet)')}`);
51
+ console.log(`${muted('shim version ')} ${shimVersion()}`);
52
+ console.log(`${muted('local version ')} ${local ?? dim('not installed')} ${localOk ? success('✓') : warning('(running from shim fallback)')}`);
53
+ console.log(`${muted('local install ')} ${LOCAL_PKG_DIR}`);
62
54
  console.log('');
63
- console.log(dim(' Force an update with: gipity update'));
64
- console.log(dim(' Disable auto-update: export DISABLE_AUTOUPDATER=1 (or set autoUpdates: false in settings.json)'));
55
+ console.log(`${muted('auto-updates ')} ${dis.disabled ? warning(`disabled (${dis.reason})`) : success('enabled')}`);
56
+ console.log(`${muted('settings file ')} ${existsSync(SETTINGS_FILE) ? SETTINGS_FILE : dim('(default)')} autoUpdates=${settings.autoUpdates}`);
57
+ console.log(`${muted('last check ')} ${rel(state.lastCheckAt)}`);
58
+ console.log(`${muted('last error ')} ${state.lastError ? clrError(state.lastError) : dim('none')}`);
59
+ console.log(`${muted('state file ')} ${existsSync(STATE_FILE) ? STATE_FILE : dim('(none yet)')}`);
60
+ console.log(`${muted('update log ')} ${existsSync(UPDATE_LOG) ? `${UPDATE_LOG} (${statSync(UPDATE_LOG).size} bytes)` : dim('(none yet)')}`);
65
61
  console.log('');
62
+ console.log(dim('Force an update with: gipity update'));
63
+ console.log(dim('Disable auto-update: export DISABLE_AUTOUPDATER=1 (or set autoUpdates: false in settings.json)'));
66
64
  });
67
65
  //# sourceMappingURL=doctor.js.map