prjct-cli 0.49.0 → 0.50.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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.50.0] - 2026-01-30
4
+
5
+ ### Features
6
+
7
+ - Unified output system with new methods - PRJ-130 (#76)
8
+
9
+
10
+ ## [0.50.0] - 2026-01-30
11
+
12
+ ### Added
13
+
14
+ - **Unified output system with new methods** (PRJ-130)
15
+ - Added `ICONS` constant for centralized icon definitions
16
+ - New methods: `info()`, `debug()`, `success()`, `list()`, `table()`, `box()`
17
+ - `debug()` only shows output when `DEBUG=1`
18
+ - Refactored existing methods to use `ICONS` for consistency
19
+
20
+
3
21
  ## [0.49.0] - 2026-01-30
4
22
 
5
23
  ### Features
@@ -1,9 +1,11 @@
1
1
  /**
2
- * Minimal Output System for prjct-cli
2
+ * Unified Output System for prjct-cli
3
3
  * Spinner while working → Single line result
4
4
  * With prjct branding
5
5
  *
6
6
  * Supports --quiet mode for CI/CD and scripting
7
+ *
8
+ * @see PRJ-130
7
9
  */
8
10
 
9
11
  import chalk from 'chalk'
@@ -14,6 +16,22 @@ import { getError } from './error-messages'
14
16
  const _FRAMES = branding.spinner.frames
15
17
  const SPEED = branding.spinner.speed
16
18
 
19
+ /**
20
+ * Centralized icons for consistent output
21
+ */
22
+ export const ICONS = {
23
+ success: chalk.green('✓'),
24
+ fail: chalk.red('✗'),
25
+ warn: chalk.yellow('⚠'),
26
+ info: chalk.blue('ℹ'),
27
+ debug: chalk.dim('🔧'),
28
+ bullet: chalk.dim('•'),
29
+ arrow: chalk.dim('→'),
30
+ check: chalk.green('✓'),
31
+ cross: chalk.red('✗'),
32
+ spinner: chalk.cyan('◐'),
33
+ } as const
34
+
17
35
  let interval: ReturnType<typeof setInterval> | null = null
18
36
  let frame = 0
19
37
 
@@ -57,6 +75,12 @@ interface Output {
57
75
  fail(msg: string): Output
58
76
  failWithHint(error: ErrorWithHint | ErrorCode): Output
59
77
  warn(msg: string): Output
78
+ info(msg: string): Output
79
+ debug(msg: string): Output
80
+ success(msg: string, metrics?: OutputMetrics): Output
81
+ list(items: string[], options?: { bullet?: string; indent?: number }): Output
82
+ table(rows: Array<Record<string, string | number>>, options?: { header?: boolean }): Output
83
+ box(title: string, content: string): Output
60
84
  stop(): Output
61
85
  step(current: number, total: number, msg: string): Output
62
86
  progress(current: number, total: number, msg?: string): Output
@@ -99,7 +123,7 @@ const out: Output = {
99
123
  suffix = chalk.dim(` [${parts.join(' | ')}]`)
100
124
  }
101
125
  }
102
- console.log(`${chalk.green('✓')} ${truncate(msg, 50)}${suffix}`)
126
+ console.log(`${ICONS.success} ${truncate(msg, 50)}${suffix}`)
103
127
  }
104
128
  return this
105
129
  },
@@ -107,7 +131,7 @@ const out: Output = {
107
131
  // Errors go to stderr even in quiet mode
108
132
  fail(msg: string) {
109
133
  this.stop()
110
- console.error(`${chalk.red('✗')} ${truncate(msg, 65)}`)
134
+ console.error(`${ICONS.fail} ${truncate(msg, 65)}`)
111
135
  return this
112
136
  },
113
137
 
@@ -116,7 +140,7 @@ const out: Output = {
116
140
  this.stop()
117
141
  const err = typeof error === 'string' ? getError(error) : error
118
142
  console.error()
119
- console.error(`${chalk.red('✗')} ${err.message}`)
143
+ console.error(`${ICONS.fail} ${err.message}`)
120
144
  if (err.file) {
121
145
  console.error(chalk.dim(` File: ${err.file}`))
122
146
  }
@@ -132,7 +156,91 @@ const out: Output = {
132
156
 
133
157
  warn(msg: string) {
134
158
  this.stop()
135
- if (!quietMode) console.log(`${chalk.yellow('⚠')} ${truncate(msg, 65)}`)
159
+ if (!quietMode) console.log(`${ICONS.warn} ${truncate(msg, 65)}`)
160
+ return this
161
+ },
162
+
163
+ // Informational message
164
+ info(msg: string) {
165
+ this.stop()
166
+ if (!quietMode) console.log(`${ICONS.info} ${msg}`)
167
+ return this
168
+ },
169
+
170
+ // Debug message (only if DEBUG=1 or DEBUG=true)
171
+ debug(msg: string) {
172
+ this.stop()
173
+ const debugEnabled = process.env.DEBUG === '1' || process.env.DEBUG === 'true'
174
+ if (!quietMode && debugEnabled) {
175
+ console.log(`${ICONS.debug} ${chalk.dim(msg)}`)
176
+ }
177
+ return this
178
+ },
179
+
180
+ // Alias for done - explicit success indicator
181
+ success(msg: string, metrics?: OutputMetrics) {
182
+ return this.done(msg, metrics)
183
+ },
184
+
185
+ // Bulleted list
186
+ list(items: string[], options: { bullet?: string; indent?: number } = {}) {
187
+ this.stop()
188
+ if (quietMode) return this
189
+ const bullet = options.bullet || ICONS.bullet
190
+ const indent = ' '.repeat(options.indent || 0)
191
+ for (const item of items) {
192
+ console.log(`${indent}${bullet} ${item}`)
193
+ }
194
+ return this
195
+ },
196
+
197
+ // Simple table output
198
+ table(rows: Array<Record<string, string | number>>, options: { header?: boolean } = {}) {
199
+ this.stop()
200
+ if (quietMode || rows.length === 0) return this
201
+
202
+ const keys = Object.keys(rows[0])
203
+ const colWidths: Record<string, number> = {}
204
+
205
+ // Calculate column widths
206
+ for (const key of keys) {
207
+ colWidths[key] = key.length
208
+ for (const row of rows) {
209
+ const val = String(row[key] ?? '')
210
+ if (val.length > colWidths[key]) colWidths[key] = val.length
211
+ }
212
+ }
213
+
214
+ // Print header if requested
215
+ if (options.header !== false) {
216
+ const headerLine = keys.map((k) => k.padEnd(colWidths[k])).join(' ')
217
+ console.log(chalk.dim(headerLine))
218
+ console.log(chalk.dim('─'.repeat(headerLine.length)))
219
+ }
220
+
221
+ // Print rows
222
+ for (const row of rows) {
223
+ const line = keys.map((k) => String(row[k] ?? '').padEnd(colWidths[k])).join(' ')
224
+ console.log(line)
225
+ }
226
+ return this
227
+ },
228
+
229
+ // Boxed content
230
+ box(title: string, content: string) {
231
+ this.stop()
232
+ if (quietMode) return this
233
+ const lines = content.split('\n')
234
+ const maxLen = Math.max(title.length, ...lines.map((l) => l.length))
235
+ const border = '─'.repeat(maxLen + 2)
236
+
237
+ console.log(chalk.dim(`┌${border}┐`))
238
+ console.log(chalk.dim('│') + ` ${chalk.bold(title.padEnd(maxLen))} ` + chalk.dim('│'))
239
+ console.log(chalk.dim(`├${border}┤`))
240
+ for (const line of lines) {
241
+ console.log(chalk.dim('│') + ` ${line.padEnd(maxLen)} ` + chalk.dim('│'))
242
+ }
243
+ console.log(chalk.dim(`└${border}┘`))
136
244
  return this
137
245
  },
138
246
 
@@ -2065,6 +2065,7 @@ var init_error_messages = __esm({
2065
2065
  var output_exports = {};
2066
2066
  __export(output_exports, {
2067
2067
  ERRORS: () => ERRORS,
2068
+ ICONS: () => ICONS,
2068
2069
  createError: () => createError,
2069
2070
  default: () => output_default,
2070
2071
  getError: () => getError,
@@ -2078,7 +2079,7 @@ function setQuietMode(enabled) {
2078
2079
  function isQuietMode() {
2079
2080
  return quietMode;
2080
2081
  }
2081
- var _FRAMES, SPEED, interval, frame, quietMode, truncate, clear, out, output_default;
2082
+ var _FRAMES, SPEED, ICONS, interval, frame, quietMode, truncate, clear, out, output_default;
2082
2083
  var init_output = __esm({
2083
2084
  "core/utils/output.ts"() {
2084
2085
  "use strict";
@@ -2087,6 +2088,18 @@ var init_output = __esm({
2087
2088
  init_error_messages();
2088
2089
  _FRAMES = branding_default.spinner.frames;
2089
2090
  SPEED = branding_default.spinner.speed;
2091
+ ICONS = {
2092
+ success: chalk2.green("\u2713"),
2093
+ fail: chalk2.red("\u2717"),
2094
+ warn: chalk2.yellow("\u26A0"),
2095
+ info: chalk2.blue("\u2139"),
2096
+ debug: chalk2.dim("\u{1F527}"),
2097
+ bullet: chalk2.dim("\u2022"),
2098
+ arrow: chalk2.dim("\u2192"),
2099
+ check: chalk2.green("\u2713"),
2100
+ cross: chalk2.red("\u2717"),
2101
+ spinner: chalk2.cyan("\u25D0")
2102
+ };
2090
2103
  interval = null;
2091
2104
  frame = 0;
2092
2105
  quietMode = false;
@@ -2127,14 +2140,14 @@ var init_output = __esm({
2127
2140
  suffix = chalk2.dim(` [${parts.join(" | ")}]`);
2128
2141
  }
2129
2142
  }
2130
- console.log(`${chalk2.green("\u2713")} ${truncate(msg, 50)}${suffix}`);
2143
+ console.log(`${ICONS.success} ${truncate(msg, 50)}${suffix}`);
2131
2144
  }
2132
2145
  return this;
2133
2146
  },
2134
2147
  // Errors go to stderr even in quiet mode
2135
2148
  fail(msg) {
2136
2149
  this.stop();
2137
- console.error(`${chalk2.red("\u2717")} ${truncate(msg, 65)}`);
2150
+ console.error(`${ICONS.fail} ${truncate(msg, 65)}`);
2138
2151
  return this;
2139
2152
  },
2140
2153
  // Rich error with context and recovery hint
@@ -2142,7 +2155,7 @@ var init_output = __esm({
2142
2155
  this.stop();
2143
2156
  const err = typeof error === "string" ? getError(error) : error;
2144
2157
  console.error();
2145
- console.error(`${chalk2.red("\u2717")} ${err.message}`);
2158
+ console.error(`${ICONS.fail} ${err.message}`);
2146
2159
  if (err.file) {
2147
2160
  console.error(chalk2.dim(` File: ${err.file}`));
2148
2161
  }
@@ -2157,7 +2170,77 @@ var init_output = __esm({
2157
2170
  },
2158
2171
  warn(msg) {
2159
2172
  this.stop();
2160
- if (!quietMode) console.log(`${chalk2.yellow("\u26A0")} ${truncate(msg, 65)}`);
2173
+ if (!quietMode) console.log(`${ICONS.warn} ${truncate(msg, 65)}`);
2174
+ return this;
2175
+ },
2176
+ // Informational message
2177
+ info(msg) {
2178
+ this.stop();
2179
+ if (!quietMode) console.log(`${ICONS.info} ${msg}`);
2180
+ return this;
2181
+ },
2182
+ // Debug message (only if DEBUG=1 or DEBUG=true)
2183
+ debug(msg) {
2184
+ this.stop();
2185
+ const debugEnabled = process.env.DEBUG === "1" || process.env.DEBUG === "true";
2186
+ if (!quietMode && debugEnabled) {
2187
+ console.log(`${ICONS.debug} ${chalk2.dim(msg)}`);
2188
+ }
2189
+ return this;
2190
+ },
2191
+ // Alias for done - explicit success indicator
2192
+ success(msg, metrics) {
2193
+ return this.done(msg, metrics);
2194
+ },
2195
+ // Bulleted list
2196
+ list(items, options = {}) {
2197
+ this.stop();
2198
+ if (quietMode) return this;
2199
+ const bullet = options.bullet || ICONS.bullet;
2200
+ const indent = " ".repeat(options.indent || 0);
2201
+ for (const item of items) {
2202
+ console.log(`${indent}${bullet} ${item}`);
2203
+ }
2204
+ return this;
2205
+ },
2206
+ // Simple table output
2207
+ table(rows, options = {}) {
2208
+ this.stop();
2209
+ if (quietMode || rows.length === 0) return this;
2210
+ const keys = Object.keys(rows[0]);
2211
+ const colWidths = {};
2212
+ for (const key of keys) {
2213
+ colWidths[key] = key.length;
2214
+ for (const row of rows) {
2215
+ const val = String(row[key] ?? "");
2216
+ if (val.length > colWidths[key]) colWidths[key] = val.length;
2217
+ }
2218
+ }
2219
+ if (options.header !== false) {
2220
+ const headerLine = keys.map((k) => k.padEnd(colWidths[k])).join(" ");
2221
+ console.log(chalk2.dim(headerLine));
2222
+ console.log(chalk2.dim("\u2500".repeat(headerLine.length)));
2223
+ }
2224
+ for (const row of rows) {
2225
+ const line = keys.map((k) => String(row[k] ?? "").padEnd(colWidths[k])).join(" ");
2226
+ console.log(line);
2227
+ }
2228
+ return this;
2229
+ },
2230
+ // Boxed content
2231
+ box(title, content) {
2232
+ this.stop();
2233
+ if (quietMode) return this;
2234
+ const lines = content.split("\n");
2235
+ const maxLen = Math.max(title.length, ...lines.map((l) => l.length));
2236
+ const border = "\u2500".repeat(maxLen + 2);
2237
+ console.log(chalk2.dim(`\u250C${border}\u2510`));
2238
+ console.log(chalk2.dim("\u2502") + ` ${chalk2.bold(title.padEnd(maxLen))} ` + chalk2.dim("\u2502"));
2239
+ console.log(chalk2.dim(`\u251C${border}\u2524`));
2240
+ for (const line of lines) {
2241
+ console.log(chalk2.dim("\u2502") + ` ${line.padEnd(maxLen)} ` + chalk2.dim("\u2502"));
2242
+ }
2243
+ console.log(chalk2.dim(`\u2514${border}\u2518`));
2161
2244
  return this;
2162
2245
  },
2163
2246
  stop() {
@@ -24220,7 +24303,7 @@ var require_package = __commonJS({
24220
24303
  "package.json"(exports, module) {
24221
24304
  module.exports = {
24222
24305
  name: "prjct-cli",
24223
- version: "0.49.0",
24306
+ version: "0.50.0",
24224
24307
  description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
24225
24308
  main: "core/index.ts",
24226
24309
  bin: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
4
  "description": "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
5
5
  "main": "core/index.ts",
6
6
  "bin": {