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