boxdown 1.0.0 → 1.2.1

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