prjct-cli 0.48.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,10 +1,39 @@
1
1
  # Changelog
2
2
 
3
- ## [0.48.0] - 2026-01-30
3
+ ## [0.50.0] - 2026-01-30
4
4
 
5
5
  ### Features
6
6
 
7
- - Read-before-write enforcement for templates - PRJ-108 (#74)
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
+
21
+ ## [0.49.0] - 2026-01-30
22
+
23
+ ### Features
24
+
25
+ - Error messages with context and recovery hints - PRJ-131 (#75)
26
+
27
+
28
+ ## [0.49.1] - 2026-01-30
29
+
30
+ ### Improved
31
+
32
+ - **Error messages with context and recovery hints** (PRJ-131)
33
+ - Created error catalog with 15+ error types
34
+ - Added `out.failWithHint()` for rich error output
35
+ - Errors now show file path and recovery hints
36
+ - Example: `✗ Project ID not found` → `💡 Run 'prjct init'`
8
37
 
9
38
 
10
39
  ## [0.49.0] - 2026-01-30
@@ -39,7 +39,7 @@ export class AnalyticsCommands extends PrjctCommandsBase {
39
39
 
40
40
  const projectId = await configManager.getProjectId(projectPath)
41
41
  if (!projectId) {
42
- out.fail('no project ID')
42
+ out.failWithHint('NO_PROJECT_ID')
43
43
  return { success: false, error: 'No project ID found' }
44
44
  }
45
45
 
@@ -79,7 +79,7 @@ export async function cleanup(
79
79
 
80
80
  const projectId = await configManager.getProjectId(projectPath)
81
81
  if (!projectId) {
82
- out.fail('no project ID')
82
+ out.failWithHint('NO_PROJECT_ID')
83
83
  return { success: false, error: 'No project ID found' }
84
84
  }
85
85
 
@@ -254,7 +254,7 @@ export class PlanningCommands extends PrjctCommandsBase {
254
254
 
255
255
  const projectId = await configManager.getProjectId(projectPath)
256
256
  if (!projectId) {
257
- out.fail('no project ID')
257
+ out.failWithHint('NO_PROJECT_ID')
258
258
  return { success: false, error: 'No project ID found' }
259
259
  }
260
260
 
@@ -325,7 +325,7 @@ export class PlanningCommands extends PrjctCommandsBase {
325
325
 
326
326
  const projectId = await configManager.getProjectId(projectPath)
327
327
  if (!projectId) {
328
- out.fail('no project ID')
328
+ out.failWithHint('NO_PROJECT_ID')
329
329
  return { success: false, error: 'No project ID found' }
330
330
  }
331
331
 
@@ -485,7 +485,7 @@ export class PlanningCommands extends PrjctCommandsBase {
485
485
 
486
486
  const projectId = await configManager.getProjectId(projectPath)
487
487
  if (!projectId) {
488
- out.fail('no project ID')
488
+ out.failWithHint('NO_PROJECT_ID')
489
489
  return { success: false, error: 'No project ID found' }
490
490
  }
491
491
 
@@ -562,7 +562,7 @@ Generated: ${new Date().toLocaleString()}
562
562
 
563
563
  const projectId = await configManager.getProjectId(projectPath)
564
564
  if (!projectId) {
565
- out.fail('no project ID')
565
+ out.failWithHint('NO_PROJECT_ID')
566
566
  return { success: false, error: 'No project ID found' }
567
567
  }
568
568
 
@@ -60,7 +60,7 @@ export class ShippingCommands extends PrjctCommandsBase {
60
60
 
61
61
  const projectId = await configManager.getProjectId(projectPath)
62
62
  if (!projectId) {
63
- out.fail('no project ID')
63
+ out.failWithHint('NO_PROJECT_ID')
64
64
  return { success: false, error: 'No project ID found' }
65
65
  }
66
66
 
@@ -28,7 +28,7 @@ export async function recover(projectPath: string = process.cwd()): Promise<Comm
28
28
  try {
29
29
  const projectId = await configManager.getProjectId(projectPath)
30
30
  if (!projectId) {
31
- out.fail('no project ID')
31
+ out.failWithHint('NO_PROJECT_ID')
32
32
  return { success: false, error: 'No project ID found' }
33
33
  }
34
34
 
@@ -85,7 +85,7 @@ export async function undo(projectPath: string = process.cwd()): Promise<Command
85
85
 
86
86
  const projectId = await configManager.getProjectId(projectPath)
87
87
  if (!projectId) {
88
- out.fail('no project ID')
88
+ out.failWithHint('NO_PROJECT_ID')
89
89
  return { success: false, error: 'No project ID found' }
90
90
  }
91
91
 
@@ -147,7 +147,7 @@ export async function undo(projectPath: string = process.cwd()): Promise<Command
147
147
  out.done('changes stashed (use /p:redo to restore)')
148
148
  return { success: true, snapshotId: stashMessage }
149
149
  } catch (gitError) {
150
- out.fail('git operation failed')
150
+ out.failWithHint('GIT_OPERATION_FAILED')
151
151
  return { success: false, error: (gitError as Error).message }
152
152
  }
153
153
  } catch (error) {
@@ -165,7 +165,7 @@ export async function redo(projectPath: string = process.cwd()): Promise<Command
165
165
 
166
166
  const projectId = await configManager.getProjectId(projectPath)
167
167
  if (!projectId) {
168
- out.fail('no project ID')
168
+ out.failWithHint('NO_PROJECT_ID')
169
169
  return { success: false, error: 'No project ID found' }
170
170
  }
171
171
 
@@ -231,7 +231,7 @@ export async function redo(projectPath: string = process.cwd()): Promise<Command
231
231
  out.done('changes restored')
232
232
  return { success: true }
233
233
  } catch (gitError) {
234
- out.fail('git operation failed')
234
+ out.failWithHint('GIT_OPERATION_FAILED')
235
235
  return { success: false, error: (gitError as Error).message }
236
236
  }
237
237
  } catch (error) {
@@ -247,7 +247,7 @@ export async function history(projectPath: string = process.cwd()): Promise<Comm
247
247
  try {
248
248
  const projectId = await configManager.getProjectId(projectPath)
249
249
  if (!projectId) {
250
- out.fail('no project ID')
250
+ out.failWithHint('NO_PROJECT_ID')
251
251
  return { success: false, error: 'No project ID found' }
252
252
  }
253
253
 
@@ -43,7 +43,7 @@ export class WorkflowCommands extends PrjctCommandsBase {
43
43
 
44
44
  const projectId = await configManager.getProjectId(projectPath)
45
45
  if (!projectId) {
46
- out.fail('no project ID')
46
+ out.failWithHint('NO_PROJECT_ID')
47
47
  return { success: false, error: 'No project ID found' }
48
48
  }
49
49
 
@@ -163,7 +163,7 @@ export class WorkflowCommands extends PrjctCommandsBase {
163
163
 
164
164
  const projectId = await configManager.getProjectId(projectPath)
165
165
  if (!projectId) {
166
- out.fail('no project ID')
166
+ out.failWithHint('NO_PROJECT_ID')
167
167
  return { success: false, error: 'No project ID found' }
168
168
  }
169
169
 
@@ -246,7 +246,7 @@ export class WorkflowCommands extends PrjctCommandsBase {
246
246
 
247
247
  const projectId = await configManager.getProjectId(projectPath)
248
248
  if (!projectId) {
249
- out.fail('no project ID')
249
+ out.failWithHint('NO_PROJECT_ID')
250
250
  return { success: false, error: 'No project ID found' }
251
251
  }
252
252
 
@@ -278,7 +278,7 @@ export class WorkflowCommands extends PrjctCommandsBase {
278
278
 
279
279
  const projectId = await configManager.getProjectId(projectPath)
280
280
  if (!projectId) {
281
- out.fail('no project ID')
281
+ out.failWithHint('NO_PROJECT_ID')
282
282
  return { success: false, error: 'No project ID found' }
283
283
  }
284
284
 
@@ -323,7 +323,7 @@ export class WorkflowCommands extends PrjctCommandsBase {
323
323
 
324
324
  const projectId = await configManager.getProjectId(projectPath)
325
325
  if (!projectId) {
326
- out.fail('no project ID')
326
+ out.failWithHint('NO_PROJECT_ID')
327
327
  return { success: false, error: 'No project ID found' }
328
328
  }
329
329
 
@@ -374,7 +374,7 @@ export class WorkflowCommands extends PrjctCommandsBase {
374
374
 
375
375
  const projectId = await configManager.getProjectId(projectPath)
376
376
  if (!projectId) {
377
- out.fail('no project ID')
377
+ out.failWithHint('NO_PROJECT_ID')
378
378
  return { success: false, error: 'No project ID found' }
379
379
  }
380
380
 
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Error Messages Catalog
3
+ *
4
+ * Centralized error messages with context and recovery hints.
5
+ * Provides consistent, helpful error output across the CLI.
6
+ *
7
+ * @see PRJ-131
8
+ * @module utils/error-messages
9
+ */
10
+
11
+ export interface ErrorWithHint {
12
+ message: string
13
+ hint?: string
14
+ file?: string
15
+ docs?: string
16
+ code?: string
17
+ }
18
+
19
+ /**
20
+ * Common error messages with recovery hints
21
+ */
22
+ export const ERRORS = {
23
+ // Project errors
24
+ NO_PROJECT: {
25
+ message: 'No prjct project found in this directory',
26
+ hint: "Run 'prjct init' to set up a new project",
27
+ file: '.prjct/prjct.config.json',
28
+ },
29
+
30
+ NO_PROJECT_ID: {
31
+ message: 'Project ID not found',
32
+ hint: "Run 'prjct init' or check .prjct/prjct.config.json",
33
+ file: '.prjct/prjct.config.json',
34
+ },
35
+
36
+ CONFIG_NOT_FOUND: {
37
+ message: 'Configuration file not found',
38
+ hint: "Run 'prjct init' to create project configuration",
39
+ file: '.prjct/prjct.config.json',
40
+ },
41
+
42
+ CONFIG_INVALID: {
43
+ message: 'Invalid configuration file',
44
+ hint: 'Check JSON syntax or delete .prjct/ and run init again',
45
+ file: '.prjct/prjct.config.json',
46
+ },
47
+
48
+ // Git errors
49
+ GIT_NOT_FOUND: {
50
+ message: 'Git repository not detected',
51
+ hint: "Run 'git init' first, then 'prjct init'",
52
+ },
53
+
54
+ GIT_NO_COMMITS: {
55
+ message: 'No commits in repository',
56
+ hint: 'Make an initial commit before using prjct',
57
+ },
58
+
59
+ GIT_DIRTY: {
60
+ message: 'Working directory has uncommitted changes',
61
+ hint: "Commit or stash changes, or use '--force' to override",
62
+ },
63
+
64
+ GIT_ON_MAIN: {
65
+ message: 'Cannot ship from main/master branch',
66
+ hint: 'Create a feature branch first: git checkout -b feature/your-feature',
67
+ },
68
+
69
+ GIT_OPERATION_FAILED: {
70
+ message: 'Git operation failed',
71
+ hint: 'Check git status and resolve any conflicts',
72
+ },
73
+
74
+ // Auth errors
75
+ GH_NOT_AUTHENTICATED: {
76
+ message: 'GitHub CLI not authenticated',
77
+ hint: "Run 'gh auth login' to authenticate",
78
+ docs: 'https://cli.github.com/manual/gh_auth_login',
79
+ },
80
+
81
+ LINEAR_NOT_CONFIGURED: {
82
+ message: 'Linear integration not configured',
83
+ hint: "Run 'p. linear setup' to configure Linear",
84
+ },
85
+
86
+ LINEAR_API_ERROR: {
87
+ message: 'Linear API error',
88
+ hint: 'Check your API key or network connection',
89
+ },
90
+
91
+ // Task errors
92
+ NO_ACTIVE_TASK: {
93
+ message: 'No active task',
94
+ hint: 'Start a task with \'p. task "description"\'',
95
+ },
96
+
97
+ TASK_ALREADY_ACTIVE: {
98
+ message: 'A task is already in progress',
99
+ hint: "Complete it with 'p. done' or pause with 'p. pause'",
100
+ },
101
+
102
+ // Sync errors
103
+ SYNC_FAILED: {
104
+ message: 'Project sync failed',
105
+ hint: 'Check file permissions and try again',
106
+ },
107
+
108
+ // Ship errors
109
+ NOTHING_TO_SHIP: {
110
+ message: 'Nothing to ship',
111
+ hint: 'Make some changes first, then run ship',
112
+ },
113
+
114
+ PR_CREATE_FAILED: {
115
+ message: 'Failed to create pull request',
116
+ hint: 'Check GitHub auth and remote configuration',
117
+ },
118
+
119
+ // Provider errors
120
+ NO_AI_PROVIDER: {
121
+ message: 'No AI provider detected',
122
+ hint: "Install Claude Code or Gemini CLI, then run 'prjct start'",
123
+ docs: 'https://prjct.app/docs',
124
+ },
125
+
126
+ PROVIDER_NOT_CONFIGURED: {
127
+ message: 'AI provider not configured for prjct',
128
+ hint: "Run 'prjct start' to configure your provider",
129
+ },
130
+
131
+ // Generic
132
+ UNKNOWN: {
133
+ message: 'An unexpected error occurred',
134
+ hint: 'Check the error details and try again',
135
+ },
136
+ } as const
137
+
138
+ export type ErrorCode = keyof typeof ERRORS
139
+
140
+ /**
141
+ * Get error with optional overrides
142
+ */
143
+ export function getError(code: ErrorCode, overrides?: Partial<ErrorWithHint>): ErrorWithHint {
144
+ const base = ERRORS[code]
145
+ return { ...base, ...overrides }
146
+ }
147
+
148
+ /**
149
+ * Create a custom error with hint
150
+ */
151
+ export function createError(
152
+ message: string,
153
+ hint?: string,
154
+ options?: { file?: string; docs?: string }
155
+ ): ErrorWithHint {
156
+ return {
157
+ message,
158
+ hint,
159
+ ...options,
160
+ }
161
+ }
@@ -1,17 +1,37 @@
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'
10
12
  import branding from './branding'
13
+ import type { ErrorCode, ErrorWithHint } from './error-messages'
14
+ import { getError } from './error-messages'
11
15
 
12
16
  const _FRAMES = branding.spinner.frames
13
17
  const SPEED = branding.spinner.speed
14
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
+
15
35
  let interval: ReturnType<typeof setInterval> | null = null
16
36
  let frame = 0
17
37
 
@@ -53,7 +73,14 @@ interface Output {
53
73
  spin(msg: string): Output
54
74
  done(msg: string, metrics?: OutputMetrics): Output
55
75
  fail(msg: string): Output
76
+ failWithHint(error: ErrorWithHint | ErrorCode): Output
56
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
57
84
  stop(): Output
58
85
  step(current: number, total: number, msg: string): Output
59
86
  progress(current: number, total: number, msg?: string): Output
@@ -96,7 +123,7 @@ const out: Output = {
96
123
  suffix = chalk.dim(` [${parts.join(' | ')}]`)
97
124
  }
98
125
  }
99
- console.log(`${chalk.green('✓')} ${truncate(msg, 50)}${suffix}`)
126
+ console.log(`${ICONS.success} ${truncate(msg, 50)}${suffix}`)
100
127
  }
101
128
  return this
102
129
  },
@@ -104,13 +131,116 @@ const out: Output = {
104
131
  // Errors go to stderr even in quiet mode
105
132
  fail(msg: string) {
106
133
  this.stop()
107
- console.error(`${chalk.red('✗')} ${truncate(msg, 65)}`)
134
+ console.error(`${ICONS.fail} ${truncate(msg, 65)}`)
135
+ return this
136
+ },
137
+
138
+ // Rich error with context and recovery hint
139
+ failWithHint(error: ErrorWithHint | ErrorCode) {
140
+ this.stop()
141
+ const err = typeof error === 'string' ? getError(error) : error
142
+ console.error()
143
+ console.error(`${ICONS.fail} ${err.message}`)
144
+ if (err.file) {
145
+ console.error(chalk.dim(` File: ${err.file}`))
146
+ }
147
+ if (err.hint) {
148
+ console.error(chalk.yellow(` 💡 ${err.hint}`))
149
+ }
150
+ if (err.docs) {
151
+ console.error(chalk.dim(` Docs: ${err.docs}`))
152
+ }
153
+ console.error()
108
154
  return this
109
155
  },
110
156
 
111
157
  warn(msg: string) {
112
158
  this.stop()
113
- 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}┘`))
114
244
  return this
115
245
  },
116
246
 
@@ -151,4 +281,6 @@ const out: Output = {
151
281
  }
152
282
 
153
283
  export type { OutputMetrics }
284
+ export type { ErrorCode, ErrorWithHint } from './error-messages'
285
+ export { createError, ERRORS, getError } from './error-messages'
154
286
  export default out
@@ -1944,10 +1944,131 @@ var init_branding = __esm({
1944
1944
  }
1945
1945
  });
1946
1946
 
1947
+ // core/utils/error-messages.ts
1948
+ function getError(code, overrides) {
1949
+ const base = ERRORS[code];
1950
+ return { ...base, ...overrides };
1951
+ }
1952
+ function createError(message, hint, options) {
1953
+ return {
1954
+ message,
1955
+ hint,
1956
+ ...options
1957
+ };
1958
+ }
1959
+ var ERRORS;
1960
+ var init_error_messages = __esm({
1961
+ "core/utils/error-messages.ts"() {
1962
+ "use strict";
1963
+ ERRORS = {
1964
+ // Project errors
1965
+ NO_PROJECT: {
1966
+ message: "No prjct project found in this directory",
1967
+ hint: "Run 'prjct init' to set up a new project",
1968
+ file: ".prjct/prjct.config.json"
1969
+ },
1970
+ NO_PROJECT_ID: {
1971
+ message: "Project ID not found",
1972
+ hint: "Run 'prjct init' or check .prjct/prjct.config.json",
1973
+ file: ".prjct/prjct.config.json"
1974
+ },
1975
+ CONFIG_NOT_FOUND: {
1976
+ message: "Configuration file not found",
1977
+ hint: "Run 'prjct init' to create project configuration",
1978
+ file: ".prjct/prjct.config.json"
1979
+ },
1980
+ CONFIG_INVALID: {
1981
+ message: "Invalid configuration file",
1982
+ hint: "Check JSON syntax or delete .prjct/ and run init again",
1983
+ file: ".prjct/prjct.config.json"
1984
+ },
1985
+ // Git errors
1986
+ GIT_NOT_FOUND: {
1987
+ message: "Git repository not detected",
1988
+ hint: "Run 'git init' first, then 'prjct init'"
1989
+ },
1990
+ GIT_NO_COMMITS: {
1991
+ message: "No commits in repository",
1992
+ hint: "Make an initial commit before using prjct"
1993
+ },
1994
+ GIT_DIRTY: {
1995
+ message: "Working directory has uncommitted changes",
1996
+ hint: "Commit or stash changes, or use '--force' to override"
1997
+ },
1998
+ GIT_ON_MAIN: {
1999
+ message: "Cannot ship from main/master branch",
2000
+ hint: "Create a feature branch first: git checkout -b feature/your-feature"
2001
+ },
2002
+ GIT_OPERATION_FAILED: {
2003
+ message: "Git operation failed",
2004
+ hint: "Check git status and resolve any conflicts"
2005
+ },
2006
+ // Auth errors
2007
+ GH_NOT_AUTHENTICATED: {
2008
+ message: "GitHub CLI not authenticated",
2009
+ hint: "Run 'gh auth login' to authenticate",
2010
+ docs: "https://cli.github.com/manual/gh_auth_login"
2011
+ },
2012
+ LINEAR_NOT_CONFIGURED: {
2013
+ message: "Linear integration not configured",
2014
+ hint: "Run 'p. linear setup' to configure Linear"
2015
+ },
2016
+ LINEAR_API_ERROR: {
2017
+ message: "Linear API error",
2018
+ hint: "Check your API key or network connection"
2019
+ },
2020
+ // Task errors
2021
+ NO_ACTIVE_TASK: {
2022
+ message: "No active task",
2023
+ hint: `Start a task with 'p. task "description"'`
2024
+ },
2025
+ TASK_ALREADY_ACTIVE: {
2026
+ message: "A task is already in progress",
2027
+ hint: "Complete it with 'p. done' or pause with 'p. pause'"
2028
+ },
2029
+ // Sync errors
2030
+ SYNC_FAILED: {
2031
+ message: "Project sync failed",
2032
+ hint: "Check file permissions and try again"
2033
+ },
2034
+ // Ship errors
2035
+ NOTHING_TO_SHIP: {
2036
+ message: "Nothing to ship",
2037
+ hint: "Make some changes first, then run ship"
2038
+ },
2039
+ PR_CREATE_FAILED: {
2040
+ message: "Failed to create pull request",
2041
+ hint: "Check GitHub auth and remote configuration"
2042
+ },
2043
+ // Provider errors
2044
+ NO_AI_PROVIDER: {
2045
+ message: "No AI provider detected",
2046
+ hint: "Install Claude Code or Gemini CLI, then run 'prjct start'",
2047
+ docs: "https://prjct.app/docs"
2048
+ },
2049
+ PROVIDER_NOT_CONFIGURED: {
2050
+ message: "AI provider not configured for prjct",
2051
+ hint: "Run 'prjct start' to configure your provider"
2052
+ },
2053
+ // Generic
2054
+ UNKNOWN: {
2055
+ message: "An unexpected error occurred",
2056
+ hint: "Check the error details and try again"
2057
+ }
2058
+ };
2059
+ __name(getError, "getError");
2060
+ __name(createError, "createError");
2061
+ }
2062
+ });
2063
+
1947
2064
  // core/utils/output.ts
1948
2065
  var output_exports = {};
1949
2066
  __export(output_exports, {
2067
+ ERRORS: () => ERRORS,
2068
+ ICONS: () => ICONS,
2069
+ createError: () => createError,
1950
2070
  default: () => output_default,
2071
+ getError: () => getError,
1951
2072
  isQuietMode: () => isQuietMode,
1952
2073
  setQuietMode: () => setQuietMode
1953
2074
  });
@@ -1958,13 +2079,27 @@ function setQuietMode(enabled) {
1958
2079
  function isQuietMode() {
1959
2080
  return quietMode;
1960
2081
  }
1961
- var _FRAMES, SPEED, interval, frame, quietMode, truncate, clear, out, output_default;
2082
+ var _FRAMES, SPEED, ICONS, interval, frame, quietMode, truncate, clear, out, output_default;
1962
2083
  var init_output = __esm({
1963
2084
  "core/utils/output.ts"() {
1964
2085
  "use strict";
1965
2086
  init_branding();
2087
+ init_error_messages();
2088
+ init_error_messages();
1966
2089
  _FRAMES = branding_default.spinner.frames;
1967
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
+ };
1968
2103
  interval = null;
1969
2104
  frame = 0;
1970
2105
  quietMode = false;
@@ -2005,19 +2140,107 @@ var init_output = __esm({
2005
2140
  suffix = chalk2.dim(` [${parts.join(" | ")}]`);
2006
2141
  }
2007
2142
  }
2008
- console.log(`${chalk2.green("\u2713")} ${truncate(msg, 50)}${suffix}`);
2143
+ console.log(`${ICONS.success} ${truncate(msg, 50)}${suffix}`);
2009
2144
  }
2010
2145
  return this;
2011
2146
  },
2012
2147
  // Errors go to stderr even in quiet mode
2013
2148
  fail(msg) {
2014
2149
  this.stop();
2015
- console.error(`${chalk2.red("\u2717")} ${truncate(msg, 65)}`);
2150
+ console.error(`${ICONS.fail} ${truncate(msg, 65)}`);
2151
+ return this;
2152
+ },
2153
+ // Rich error with context and recovery hint
2154
+ failWithHint(error) {
2155
+ this.stop();
2156
+ const err = typeof error === "string" ? getError(error) : error;
2157
+ console.error();
2158
+ console.error(`${ICONS.fail} ${err.message}`);
2159
+ if (err.file) {
2160
+ console.error(chalk2.dim(` File: ${err.file}`));
2161
+ }
2162
+ if (err.hint) {
2163
+ console.error(chalk2.yellow(` \u{1F4A1} ${err.hint}`));
2164
+ }
2165
+ if (err.docs) {
2166
+ console.error(chalk2.dim(` Docs: ${err.docs}`));
2167
+ }
2168
+ console.error();
2016
2169
  return this;
2017
2170
  },
2018
2171
  warn(msg) {
2019
2172
  this.stop();
2020
- 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`));
2021
2244
  return this;
2022
2245
  },
2023
2246
  stop() {
@@ -16738,7 +16961,7 @@ Generated: ${(/* @__PURE__ */ new Date()).toLocaleString()}
16738
16961
  }
16739
16962
  const projectId = await config_manager_default.getProjectId(projectPath);
16740
16963
  if (!projectId) {
16741
- output_default.fail("no project ID");
16964
+ output_default.failWithHint("NO_PROJECT_ID");
16742
16965
  return { success: false, error: "No project ID found" };
16743
16966
  }
16744
16967
  output_default.spin(`planning ${description}...`);
@@ -16795,7 +17018,7 @@ Generated: ${(/* @__PURE__ */ new Date()).toLocaleString()}
16795
17018
  }
16796
17019
  const projectId = await config_manager_default.getProjectId(projectPath);
16797
17020
  if (!projectId) {
16798
- output_default.fail("no project ID");
17021
+ output_default.failWithHint("NO_PROJECT_ID");
16799
17022
  return { success: false, error: "No project ID found" };
16800
17023
  }
16801
17024
  output_default.spin("tracking bug...");
@@ -16918,7 +17141,7 @@ ${"=".repeat(60)}`);
16918
17141
  }
16919
17142
  const projectId = await config_manager_default.getProjectId(projectPath);
16920
17143
  if (!projectId) {
16921
- output_default.fail("no project ID");
17144
+ output_default.failWithHint("NO_PROJECT_ID");
16922
17145
  return { success: false, error: "No project ID found" };
16923
17146
  }
16924
17147
  const wordCount = description.split(/\s+/).length;
@@ -16976,7 +17199,7 @@ Generated: ${(/* @__PURE__ */ new Date()).toLocaleString()}
16976
17199
  if (!initResult.success) return initResult;
16977
17200
  const projectId = await config_manager_default.getProjectId(projectPath);
16978
17201
  if (!projectId) {
16979
- output_default.fail("no project ID");
17202
+ output_default.failWithHint("NO_PROJECT_ID");
16980
17203
  return { success: false, error: "No project ID found" };
16981
17204
  }
16982
17205
  if (!featureName) {
@@ -21067,7 +21290,7 @@ var init_analytics = __esm({
21067
21290
  if (!initResult.success) return initResult;
21068
21291
  const projectId = await config_manager_default.getProjectId(projectPath);
21069
21292
  if (!projectId) {
21070
- output_default.fail("no project ID");
21293
+ output_default.failWithHint("NO_PROJECT_ID");
21071
21294
  return { success: false, error: "No project ID found" };
21072
21295
  }
21073
21296
  const projectName = path45.basename(projectPath);
@@ -21507,7 +21730,7 @@ async function cleanup(options = {}, projectPath = process.cwd()) {
21507
21730
  output_default.spin("cleaning up...");
21508
21731
  const projectId = await config_manager_default.getProjectId(projectPath);
21509
21732
  if (!projectId) {
21510
- output_default.fail("no project ID");
21733
+ output_default.failWithHint("NO_PROJECT_ID");
21511
21734
  return { success: false, error: "No project ID found" };
21512
21735
  }
21513
21736
  const cleaned = [];
@@ -21666,7 +21889,7 @@ async function recover(projectPath = process.cwd()) {
21666
21889
  try {
21667
21890
  const projectId = await config_manager_default.getProjectId(projectPath);
21668
21891
  if (!projectId) {
21669
- output_default.fail("no project ID");
21892
+ output_default.failWithHint("NO_PROJECT_ID");
21670
21893
  return { success: false, error: "No project ID found" };
21671
21894
  }
21672
21895
  output_default.spin("checking for abandoned sessions...");
@@ -21710,7 +21933,7 @@ async function undo(projectPath = process.cwd()) {
21710
21933
  output_default.spin("creating undo point...");
21711
21934
  const projectId = await config_manager_default.getProjectId(projectPath);
21712
21935
  if (!projectId) {
21713
- output_default.fail("no project ID");
21936
+ output_default.failWithHint("NO_PROJECT_ID");
21714
21937
  return { success: false, error: "No project ID found" };
21715
21938
  }
21716
21939
  const snapshotsPath = path49.join(path_manager_default.getGlobalProjectPath(projectId), "snapshots");
@@ -21755,7 +21978,7 @@ async function undo(projectPath = process.cwd()) {
21755
21978
  output_default.done("changes stashed (use /p:redo to restore)");
21756
21979
  return { success: true, snapshotId: stashMessage };
21757
21980
  } catch (gitError) {
21758
- output_default.fail("git operation failed");
21981
+ output_default.failWithHint("GIT_OPERATION_FAILED");
21759
21982
  return { success: false, error: gitError.message };
21760
21983
  }
21761
21984
  } catch (error) {
@@ -21768,7 +21991,7 @@ async function redo(projectPath = process.cwd()) {
21768
21991
  output_default.spin("restoring changes...");
21769
21992
  const projectId = await config_manager_default.getProjectId(projectPath);
21770
21993
  if (!projectId) {
21771
- output_default.fail("no project ID");
21994
+ output_default.failWithHint("NO_PROJECT_ID");
21772
21995
  return { success: false, error: "No project ID found" };
21773
21996
  }
21774
21997
  const snapshotsPath = path49.join(path_manager_default.getGlobalProjectPath(projectId), "snapshots");
@@ -21816,7 +22039,7 @@ async function redo(projectPath = process.cwd()) {
21816
22039
  output_default.done("changes restored");
21817
22040
  return { success: true };
21818
22041
  } catch (gitError) {
21819
- output_default.fail("git operation failed");
22042
+ output_default.failWithHint("GIT_OPERATION_FAILED");
21820
22043
  return { success: false, error: gitError.message };
21821
22044
  }
21822
22045
  } catch (error) {
@@ -21828,7 +22051,7 @@ async function history(projectPath = process.cwd()) {
21828
22051
  try {
21829
22052
  const projectId = await config_manager_default.getProjectId(projectPath);
21830
22053
  if (!projectId) {
21831
- output_default.fail("no project ID");
22054
+ output_default.failWithHint("NO_PROJECT_ID");
21832
22055
  return { success: false, error: "No project ID found" };
21833
22056
  }
21834
22057
  const snapshotsPath = path49.join(path_manager_default.getGlobalProjectPath(projectId), "snapshots");
@@ -22483,7 +22706,7 @@ ${result.stderr}`.trim();
22483
22706
  if (!initResult.success) return initResult;
22484
22707
  const projectId = await config_manager_default.getProjectId(projectPath);
22485
22708
  if (!projectId) {
22486
- output_default.fail("no project ID");
22709
+ output_default.failWithHint("NO_PROJECT_ID");
22487
22710
  return { success: false, error: "No project ID found" };
22488
22711
  }
22489
22712
  let featureName = feature;
@@ -23557,7 +23780,7 @@ var init_workflow = __esm({
23557
23780
  if (!initResult.success) return initResult;
23558
23781
  const projectId = await config_manager_default.getProjectId(projectPath);
23559
23782
  if (!projectId) {
23560
- output_default.fail("no project ID");
23783
+ output_default.failWithHint("NO_PROJECT_ID");
23561
23784
  return { success: false, error: "No project ID found" };
23562
23785
  }
23563
23786
  if (task) {
@@ -23649,7 +23872,7 @@ var init_workflow = __esm({
23649
23872
  if (!initResult.success) return initResult;
23650
23873
  const projectId = await config_manager_default.getProjectId(projectPath);
23651
23874
  if (!projectId) {
23652
- output_default.fail("no project ID");
23875
+ output_default.failWithHint("NO_PROJECT_ID");
23653
23876
  return { success: false, error: "No project ID found" };
23654
23877
  }
23655
23878
  const currentTask = await stateStorage.getCurrentTask(projectId);
@@ -23715,7 +23938,7 @@ var init_workflow = __esm({
23715
23938
  if (!initResult.success) return initResult;
23716
23939
  const projectId = await config_manager_default.getProjectId(projectPath);
23717
23940
  if (!projectId) {
23718
- output_default.fail("no project ID");
23941
+ output_default.failWithHint("NO_PROJECT_ID");
23719
23942
  return { success: false, error: "No project ID found" };
23720
23943
  }
23721
23944
  const tasks = await queueStorage.getActiveTasks(projectId);
@@ -23740,7 +23963,7 @@ var init_workflow = __esm({
23740
23963
  if (!initResult.success) return initResult;
23741
23964
  const projectId = await config_manager_default.getProjectId(projectPath);
23742
23965
  if (!projectId) {
23743
- output_default.fail("no project ID");
23966
+ output_default.failWithHint("NO_PROJECT_ID");
23744
23967
  return { success: false, error: "No project ID found" };
23745
23968
  }
23746
23969
  const currentTask = await stateStorage.getCurrentTask(projectId);
@@ -23773,7 +23996,7 @@ var init_workflow = __esm({
23773
23996
  if (!initResult.success) return initResult;
23774
23997
  const projectId = await config_manager_default.getProjectId(projectPath);
23775
23998
  if (!projectId) {
23776
- output_default.fail("no project ID");
23999
+ output_default.failWithHint("NO_PROJECT_ID");
23777
24000
  return { success: false, error: "No project ID found" };
23778
24001
  }
23779
24002
  const currentTask = await stateStorage.getCurrentTask(projectId);
@@ -23811,7 +24034,7 @@ var init_workflow = __esm({
23811
24034
  if (!initResult.success) return initResult;
23812
24035
  const projectId = await config_manager_default.getProjectId(projectPath);
23813
24036
  if (!projectId) {
23814
- output_default.fail("no project ID");
24037
+ output_default.failWithHint("NO_PROJECT_ID");
23815
24038
  return { success: false, error: "No project ID found" };
23816
24039
  }
23817
24040
  if (!input) {
@@ -24080,7 +24303,7 @@ var require_package = __commonJS({
24080
24303
  "package.json"(exports, module) {
24081
24304
  module.exports = {
24082
24305
  name: "prjct-cli",
24083
- version: "0.48.0",
24306
+ version: "0.50.0",
24084
24307
  description: "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
24085
24308
  main: "core/index.ts",
24086
24309
  bin: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "0.48.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": {