boxdown 1.0.0 → 1.2.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.
Files changed (69) hide show
  1. package/README.md +135 -12
  2. package/assets/devcontainer/README.md +49 -17
  3. package/assets/devcontainer/devcontainer.json +24 -7
  4. package/assets/devcontainer/hooks/initialize.sh +32 -0
  5. package/assets/devcontainer/hooks/post-create.sh +59 -12
  6. package/assets/devcontainer/hooks/post-start.sh +21 -4
  7. package/assets/devcontainer/ssh-config-install.sh +12 -3
  8. package/assets/devcontainer/start.sh +721 -44
  9. package/assets/devcontainer/utils/coding-agent-cli-update.sh +267 -3
  10. package/assets/devcontainer/utils/deps-install.sh +68 -0
  11. package/assets/devcontainer/utils/git-config-bootstrap.sh +128 -0
  12. package/assets/devcontainer/utils/git-signing-bootstrap.sh +56 -0
  13. package/assets/devcontainer/utils/python-bootstrap.sh +69 -0
  14. package/assets/devcontainer/utils/ssh-bootstrap.sh +9 -0
  15. package/dist/bin/cli.cjs +1 -1
  16. package/dist/bin/cli.mjs +1 -1
  17. package/dist/main-Df4E8ARj.cjs +5498 -0
  18. package/dist/main-XMBsKjIK.mjs +5458 -0
  19. package/dist/main-XMBsKjIK.mjs.map +1 -0
  20. package/dist/main.cjs +5 -2
  21. package/dist/main.d.cts +495 -4
  22. package/dist/main.d.cts.map +1 -1
  23. package/dist/main.d.mts +495 -4
  24. package/dist/main.d.mts.map +1 -1
  25. package/dist/main.mjs +2 -2
  26. package/docs/README.md +1 -0
  27. package/docs/architecture.md +32 -0
  28. package/docs/development.md +13 -6
  29. package/docs/features/README.md +2 -0
  30. package/docs/features/commit-signing.md +23 -0
  31. package/docs/features/generated-config-and-state.md +57 -4
  32. package/docs/features/github-auth-refresh.md +12 -0
  33. package/docs/features/lifecycle.md +97 -11
  34. package/docs/features/setup.md +66 -0
  35. package/docs/features/ssh-config-and-proxy.md +228 -7
  36. package/docs/features/start-and-shell.md +40 -5
  37. package/docs/superpowers/plans/2026-07-11-default-commit-signing.md +110 -0
  38. package/docs/superpowers/plans/2026-07-11-progress-ci-regression.md +125 -0
  39. package/docs/superpowers/specs/2026-07-11-default-commit-signing-design.md +396 -0
  40. package/docs/superpowers/specs/2026-07-11-progress-ci-regression-design.md +43 -0
  41. package/docs/testing.md +35 -2
  42. package/docs/todo.md +2 -2
  43. package/package.json +1 -1
  44. package/src/claude-app-config.ts +304 -0
  45. package/src/cli-style.ts +43 -0
  46. package/src/codex-app-config.ts +656 -0
  47. package/src/config.ts +68 -10
  48. package/src/constants.ts +5 -0
  49. package/src/devcontainer.ts +500 -64
  50. package/src/doctor.ts +195 -30
  51. package/src/git-signing.ts +87 -0
  52. package/src/interactive-prompts.ts +692 -0
  53. package/src/list.ts +16 -2
  54. package/src/logging.ts +154 -0
  55. package/src/main.ts +1213 -63
  56. package/src/metadata.ts +52 -3
  57. package/src/package-info.ts +18 -0
  58. package/src/paths.ts +50 -10
  59. package/src/process.ts +80 -2
  60. package/src/progress.ts +593 -0
  61. package/src/purge.ts +208 -0
  62. package/src/shell.ts +19 -0
  63. package/src/ssh-config.ts +134 -16
  64. package/src/ssh-install-targets.ts +111 -0
  65. package/src/ssh-key.ts +53 -13
  66. package/src/status.ts +5 -0
  67. package/dist/main-BuEptwlL.cjs +0 -1707
  68. package/dist/main-ZFTrSVgt.mjs +0 -1685
  69. package/dist/main-ZFTrSVgt.mjs.map +0 -1
package/src/logging.ts ADDED
@@ -0,0 +1,154 @@
1
+ import { appendFileSync, mkdirSync } from 'node:fs'
2
+ import { dirname } from 'node:path'
3
+
4
+ import type { WorkspaceContext } from './paths.ts'
5
+
6
+ export type LogStreamName = 'stdout' | 'stderr' | 'boxdown'
7
+
8
+ export interface WorkspaceCommandLoggerOptions {
9
+ redactions?: string[]
10
+ now?: () => Date
11
+ }
12
+
13
+ export interface LoggedCommand {
14
+ stream: (stream: Extract<LogStreamName, 'stdout' | 'stderr'>, chunk: Buffer | string) => void
15
+ error: (error: unknown) => void
16
+ finish: (code: number) => void
17
+ }
18
+
19
+ function errorMessage (error: unknown): string {
20
+ return error instanceof Error ? error.message : String(error)
21
+ }
22
+
23
+ function argvText (command: string, args: string[]): string {
24
+ return JSON.stringify([command, ...args])
25
+ }
26
+
27
+ export class WorkspaceCommandLogger {
28
+ readonly logPath: string
29
+ readonly workspaceFolder: string
30
+ readonly #redactions: string[]
31
+ readonly #now: () => Date
32
+ #disabled = false
33
+
34
+ constructor (context: Pick<WorkspaceContext, 'workspaceFolder' | 'workspaceLogPath'>, options: WorkspaceCommandLoggerOptions = {}) {
35
+ this.logPath = context.workspaceLogPath
36
+ this.workspaceFolder = context.workspaceFolder
37
+ this.#redactions = options.redactions?.filter((value) => value.length > 0) ?? []
38
+ this.#now = options.now ?? (() => new Date())
39
+ }
40
+
41
+ addRedaction (value: string): void {
42
+ if (value.length > 0) {
43
+ this.#redactions.push(value)
44
+ }
45
+ }
46
+
47
+ disable (): void {
48
+ this.#disabled = true
49
+ }
50
+
51
+ section (title: string, details: Record<string, string | number | boolean | undefined> = {}): void {
52
+ this.#append([
53
+ '',
54
+ `${this.#timestamp()} === ${this.#redact(title)} ===`,
55
+ `${this.#timestamp()} workspace: ${this.#redact(this.workspaceFolder)}`,
56
+ ...Object.entries(details)
57
+ .filter((entry): entry is [string, string | number | boolean] => entry[1] !== undefined)
58
+ .map(([key, value]) => `${this.#timestamp()} ${key}: ${this.#redact(String(value))}`)
59
+ ])
60
+ }
61
+
62
+ boxdown (chunk: Buffer | string): void {
63
+ this.#stream('boxdown', chunk)
64
+ }
65
+
66
+ startCommand (command: string, args: string[], options: { cwd?: string } = {}): LoggedCommand {
67
+ const startedAt = Date.now()
68
+
69
+ this.#append([
70
+ `${this.#timestamp()} command start: ${this.#redact(argvText(command, args))}`,
71
+ ...(options.cwd === undefined ? [] : [`${this.#timestamp()} cwd: ${this.#redact(options.cwd)}`])
72
+ ])
73
+
74
+ return {
75
+ stream: (stream, chunk) => {
76
+ this.#stream(stream, chunk)
77
+ },
78
+ error: (error) => {
79
+ this.#append([`${this.#timestamp()} command error: ${this.#redact(errorMessage(error))}`])
80
+ },
81
+ finish: (code) => {
82
+ this.#append([`${this.#timestamp()} command exit: ${code} (${Date.now() - startedAt}ms)`])
83
+ }
84
+ }
85
+ }
86
+
87
+ #timestamp (): string {
88
+ return `[${this.#now().toISOString()}]`
89
+ }
90
+
91
+ #redact (value: string): string {
92
+ return this.#redactions.reduce((current, redaction) => current.replaceAll(redaction, '[redacted]'), value)
93
+ }
94
+
95
+ #stream (stream: LogStreamName, chunk: Buffer | string): void {
96
+ const text = this.#redact(Buffer.isBuffer(chunk) ? chunk.toString('utf8') : chunk)
97
+ const lines = text.split(/\r?\n/u)
98
+
99
+ if (lines.at(-1) === '') {
100
+ lines.pop()
101
+ }
102
+
103
+ if (lines.length === 0) {
104
+ return
105
+ }
106
+
107
+ this.#append(lines.map((line) => `${this.#timestamp()} [${stream}] ${line}`))
108
+ }
109
+
110
+ #append (lines: string[]): void {
111
+ if (this.#disabled) {
112
+ return
113
+ }
114
+
115
+ try {
116
+ mkdirSync(dirname(this.logPath), { recursive: true })
117
+ appendFileSync(this.logPath, `${lines.join('\n')}\n`)
118
+ } catch {
119
+ this.#disabled = true
120
+ }
121
+ }
122
+ }
123
+
124
+ export function createWorkspaceCommandLogger (
125
+ context: Pick<WorkspaceContext, 'workspaceFolder' | 'workspaceLogPath'>,
126
+ options: WorkspaceCommandLoggerOptions = {}
127
+ ): WorkspaceCommandLogger {
128
+ return new WorkspaceCommandLogger(context, options)
129
+ }
130
+
131
+ export async function withLoggedProcessOutput<T> (
132
+ logger: WorkspaceCommandLogger,
133
+ action: () => Promise<T>
134
+ ): Promise<T> {
135
+ const stdoutWrite = process.stdout.write
136
+ const stderrWrite = process.stderr.write
137
+
138
+ process.stdout.write = function patchedStdoutWrite (this: typeof process.stdout, chunk: string | Uint8Array, ...args: unknown[]): boolean {
139
+ logger.boxdown(Buffer.isBuffer(chunk) ? chunk : String(chunk))
140
+ return stdoutWrite.call(this, chunk, ...args as []) as boolean
141
+ } as typeof process.stdout.write
142
+
143
+ process.stderr.write = function patchedStderrWrite (this: typeof process.stderr, chunk: string | Uint8Array, ...args: unknown[]): boolean {
144
+ logger.boxdown(Buffer.isBuffer(chunk) ? chunk : String(chunk))
145
+ return stderrWrite.call(this, chunk, ...args as []) as boolean
146
+ } as typeof process.stderr.write
147
+
148
+ try {
149
+ return await action()
150
+ } finally {
151
+ process.stdout.write = stdoutWrite
152
+ process.stderr.write = stderrWrite
153
+ }
154
+ }