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/config.ts CHANGED
@@ -3,12 +3,17 @@ import { join } from 'node:path'
3
3
 
4
4
  import {
5
5
  BOXDOWN_CONTAINER_AGENTS_DIR,
6
+ BOXDOWN_CONTAINER_CODEX_AUTH_PATH,
7
+ BOXDOWN_CONTAINER_CODEX_DIR,
6
8
  BOXDOWN_CONTAINER_DEVCONTAINER_DIR,
9
+ BOXDOWN_CONTAINER_GITCONFIG_PATH,
10
+ BOXDOWN_CONTAINER_HOST_GITCONFIG_DIR,
7
11
  BOXDOWN_CONTAINER_SSH_DIR,
8
12
  BOXDOWN_CONTAINER_SSH_PUBLIC_KEY_PATH
9
13
  } from './constants.ts'
10
14
  import { parseJsonc } from './jsonc.ts'
11
15
  import type { WorkspaceContext } from './paths.ts'
16
+ import type { GitSigningPlan } from './git-signing.ts'
12
17
  import { shellQuote } from './shell.ts'
13
18
 
14
19
  export interface DevcontainerConfig {
@@ -35,42 +40,95 @@ function directoryExists (path: string): boolean {
35
40
  }
36
41
  }
37
42
 
38
- export function buildGeneratedDevcontainerConfig (context: WorkspaceContext): DevcontainerConfig {
43
+ function fileExists (path: string): boolean {
44
+ try {
45
+ return statSync(path).isFile()
46
+ } catch {
47
+ return false
48
+ }
49
+ }
50
+
51
+ function mountHasTarget (mount: string, target: string): boolean {
52
+ return mount.split(',').some((part) => part.trim() === `target=${target}`)
53
+ }
54
+
55
+ function hasMountTarget (mounts: string[], target: string): boolean {
56
+ return mounts.some((mount) => mountHasTarget(mount, target))
57
+ }
58
+
59
+ export function buildGeneratedDevcontainerConfig (context: WorkspaceContext, signing?: GitSigningPlan): DevcontainerConfig {
39
60
  const baseConfig = readBaseDevcontainerConfig(context.assetsDevcontainerDir)
40
61
  const mounts = Array.isArray(baseConfig.mounts)
41
- ? baseConfig.mounts.filter((mount): mount is string => typeof mount === 'string')
62
+ ? baseConfig.mounts
63
+ .filter((mount): mount is string => typeof mount === 'string')
64
+ .filter((mount) => !mountHasTarget(mount, BOXDOWN_CONTAINER_GITCONFIG_PATH))
42
65
  : []
43
66
 
44
67
  const boxdownMounts = [
45
68
  `type=bind,source=${context.assetsDevcontainerDir},target=${BOXDOWN_CONTAINER_DEVCONTAINER_DIR},readonly`,
46
- `type=bind,source=${context.sshPublicKeyRuntimeDir},target=${BOXDOWN_CONTAINER_SSH_DIR},readonly`
69
+ `type=bind,source=${context.sshPublicKeyRuntimeDir},target=${BOXDOWN_CONTAINER_SSH_DIR},readonly`,
70
+ `type=bind,source=${context.hostGitconfigSnapshotDir},target=${BOXDOWN_CONTAINER_HOST_GITCONFIG_DIR},readonly`
47
71
  ]
48
72
 
49
73
  if (
50
74
  directoryExists(context.hostAgentsDir) &&
51
- !mounts.some((mount) => mount.includes(`target=${BOXDOWN_CONTAINER_AGENTS_DIR}`))
75
+ !hasMountTarget(mounts, BOXDOWN_CONTAINER_AGENTS_DIR)
52
76
  ) {
53
77
  boxdownMounts.push(`type=bind,source=${context.hostAgentsDir},target=${BOXDOWN_CONTAINER_AGENTS_DIR},readonly`)
54
78
  }
55
79
 
80
+ if (signing?.enabled === true && signing.agentSocketSource !== undefined) {
81
+ boxdownMounts.push(`type=bind,source=${signing.agentSocketSource},target=/run/boxdown/ssh-agent.sock`)
82
+ boxdownMounts.push(`type=bind,source=${context.gitSigningStateDir},target=/opt/boxdown/state/git-signing,readonly`)
83
+ }
84
+
85
+ if (
86
+ fileExists(context.hostCodexAuthPath) &&
87
+ !hasMountTarget(mounts, BOXDOWN_CONTAINER_CODEX_DIR) &&
88
+ !hasMountTarget(mounts, BOXDOWN_CONTAINER_CODEX_AUTH_PATH)
89
+ ) {
90
+ boxdownMounts.push(`type=bind,source=${context.hostCodexAuthPath},target=${BOXDOWN_CONTAINER_CODEX_AUTH_PATH},readonly`)
91
+ }
92
+
56
93
  return {
57
94
  ...baseConfig,
58
95
  name: `Boxdown: ${context.workspaceBasename}`,
59
96
  mounts: [...mounts, ...boxdownMounts],
60
- initializeCommand: `BOXDOWN_WORKSPACE_FOLDER=${shellQuote(context.workspaceFolder)} bash ${shellQuote(join(context.assetsDevcontainerDir, 'hooks', 'initialize.sh'))}`,
61
- postCreateCommand: `bash ${shellQuote(`${BOXDOWN_CONTAINER_DEVCONTAINER_DIR}/hooks/post-create.sh`)}`,
62
- postStartCommand: `bash ${shellQuote(`${BOXDOWN_CONTAINER_DEVCONTAINER_DIR}/hooks/post-start.sh`)}`,
97
+ initializeCommand: [
98
+ `BOXDOWN_WORKSPACE_FOLDER=${shellQuote(context.workspaceFolder)}`,
99
+ `BOXDOWN_HOST_GITCONFIG_PATH=${shellQuote(context.hostGitconfigPath)}`,
100
+ `BOXDOWN_HOST_GITCONFIG_SNAPSHOT_PATH=${shellQuote(context.hostGitconfigSnapshotPath)}`,
101
+ `BOXDOWN_PROGRESS=${shellQuote('${localEnv:BOXDOWN_PROGRESS}')}`,
102
+ `BOXDOWN_VERBOSE=${shellQuote('${localEnv:BOXDOWN_VERBOSE}')}`,
103
+ 'bash',
104
+ shellQuote(join(context.assetsDevcontainerDir, 'hooks', 'initialize.sh'))
105
+ ].join(' '),
106
+ postCreateCommand: [
107
+ `BOXDOWN_PROGRESS=${shellQuote('${localEnv:BOXDOWN_PROGRESS}')}`,
108
+ `BOXDOWN_VERBOSE=${shellQuote('${localEnv:BOXDOWN_VERBOSE}')}`,
109
+ 'bash',
110
+ shellQuote(`${BOXDOWN_CONTAINER_DEVCONTAINER_DIR}/hooks/post-create.sh`)
111
+ ].join(' '),
112
+ postStartCommand: [
113
+ `BOXDOWN_PROGRESS=${shellQuote('${localEnv:BOXDOWN_PROGRESS}')}`,
114
+ `BOXDOWN_VERBOSE=${shellQuote('${localEnv:BOXDOWN_VERBOSE}')}`,
115
+ 'bash',
116
+ shellQuote(`${BOXDOWN_CONTAINER_DEVCONTAINER_DIR}/hooks/post-start.sh`)
117
+ ].join(' '),
63
118
  containerEnv: {
64
119
  ...(baseConfig.containerEnv ?? {}),
65
120
  BOXDOWN_CONTAINER_WORKSPACE_FOLDER: '/workspaces/${localWorkspaceFolderBasename}',
66
121
  BOXDOWN_WORKSPACE_BASENAME: '${localWorkspaceFolderBasename}',
67
- DEVCONTAINER_SSH_PUBLIC_KEY_FILE: BOXDOWN_CONTAINER_SSH_PUBLIC_KEY_PATH
122
+ DEVCONTAINER_SSH_PUBLIC_KEY_FILE: BOXDOWN_CONTAINER_SSH_PUBLIC_KEY_PATH,
123
+ BOXDOWN_GIT_SIGNING_ENABLED: signing?.enabled === true ? '1' : '0',
124
+ BOXDOWN_GIT_SIGNING_KEY_PATH: '/opt/boxdown/state/git-signing/signing-key.pub',
125
+ ...(signing?.enabled === true ? { SSH_AUTH_SOCK: '/run/boxdown/ssh-agent.sock' } : {})
68
126
  }
69
127
  }
70
128
  }
71
129
 
72
- export function writeGeneratedDevcontainerConfig (context: WorkspaceContext): DevcontainerConfig {
73
- const config = buildGeneratedDevcontainerConfig(context)
130
+ export function writeGeneratedDevcontainerConfig (context: WorkspaceContext, signing?: GitSigningPlan): DevcontainerConfig {
131
+ const config = buildGeneratedDevcontainerConfig(context, signing)
74
132
  mkdirSync(context.workspaceCacheDir, { recursive: true })
75
133
  writeFileSync(context.generatedConfigPath, `${JSON.stringify(config, null, 2)}\n`)
76
134
  return config
package/src/constants.ts CHANGED
@@ -2,8 +2,13 @@ export const DEVCONTAINER_CLI_VERSION = '0.84.1'
2
2
 
3
3
  export const BOXDOWN_CONTAINER_DEVCONTAINER_DIR = '/opt/boxdown/devcontainer'
4
4
  export const BOXDOWN_CONTAINER_STATE_DIR = '/opt/boxdown/state'
5
+ export const BOXDOWN_CONTAINER_HOST_GITCONFIG_DIR = `${BOXDOWN_CONTAINER_STATE_DIR}/host-gitconfig`
6
+ export const BOXDOWN_CONTAINER_HOST_GITCONFIG_PATH = `${BOXDOWN_CONTAINER_HOST_GITCONFIG_DIR}/.gitconfig`
5
7
  export const BOXDOWN_CONTAINER_SSH_DIR = `${BOXDOWN_CONTAINER_STATE_DIR}/ssh`
6
8
  export const BOXDOWN_CONTAINER_SSH_PUBLIC_KEY_PATH = `${BOXDOWN_CONTAINER_SSH_DIR}/id_ed25519.pub`
7
9
  export const BOXDOWN_CONTAINER_AGENTS_DIR = '/home/node/.agents'
10
+ export const BOXDOWN_CONTAINER_GITCONFIG_PATH = '/home/node/.gitconfig'
11
+ export const BOXDOWN_CONTAINER_CODEX_DIR = '/home/node/.codex'
12
+ export const BOXDOWN_CONTAINER_CODEX_AUTH_PATH = `${BOXDOWN_CONTAINER_CODEX_DIR}/auth.json`
8
13
 
9
14
  export const PACKAGE_NAME = 'boxdown'