jackherd 0.0.1 → 1.0.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 +61 -0
- package/LICENSE +21 -0
- package/NOTICE +38 -0
- package/README.md +1121 -2
- package/assets/branding/README.md +62 -0
- package/assets/branding/jackherd-icon-256.png +0 -0
- package/assets/branding/jackherd-icon-512.png +0 -0
- package/assets/branding/jackherd-icon-64.png +0 -0
- package/assets/branding/jackherd-icon-small-32.png +0 -0
- package/assets/branding/jackherd-icon-small-64.png +0 -0
- package/assets/branding/jackherd-icon-small.svg +1 -0
- package/assets/branding/jackherd-icon.svg +1 -0
- package/assets/branding/jackherd-logo-1024.png +0 -0
- package/assets/branding/jackherd-logo-512.png +0 -0
- package/assets/branding/jackherd-logo-dark-1024.png +0 -0
- package/assets/branding/jackherd-logo-dark-512.png +0 -0
- package/assets/branding/jackherd-logo-dark.svg +1 -0
- package/assets/branding/jackherd-logo-mark-1024.png +0 -0
- package/assets/branding/jackherd-logo-mark-512.png +0 -0
- package/assets/branding/jackherd-logo-mark.svg +1 -0
- package/assets/branding/jackherd-logo.svg +1 -0
- package/dist/chunk-R6MGHCD7.js +93 -0
- package/dist/config.d.ts +167 -0
- package/dist/config.js +401 -0
- package/dist/plan-format.d.ts +325 -0
- package/dist/plan-format.js +636 -0
- package/docs/lanes.mmd +9 -0
- package/docs/lanes.svg +1 -0
- package/docs/skills.mmd +10 -0
- package/docs/skills.svg +1 -0
- package/docs/workflow.svg +172 -0
- package/herdr-plugin/herdr-plugin.toml +22 -0
- package/herdr-plugin/open-plans.ts +43 -0
- package/herdr-plugin/restore-plans.ts +107 -0
- package/install.sh +157 -0
- package/kit/hooks/hooks/git-guard/decision.ts +438 -0
- package/kit/hooks/hooks/git-guard/index.ts +38 -0
- package/kit/hooks/hooks/git-guard/shell.ts +407 -0
- package/kit/hooks/hooks/watch-guard/background.ts +36 -0
- package/kit/hooks/hooks/watch-guard/decision.ts +53 -0
- package/kit/hooks/hooks/watch-guard/index.ts +11 -0
- package/kit/hooks/hooks/watch-guard/shell-input.ts +32 -0
- package/kit/hooks/hooks/watch-guard/starts.ts +154 -0
- package/kit/hooks/hooks/watch-guard/text-fallback.ts +40 -0
- package/kit/hooks/hooks.json +30 -0
- package/kit/hooks/statusline.sh +72 -0
- package/kit/review/agents/devops.md +11 -0
- package/kit/review/agents/public-content.md +39 -0
- package/kit/review/agents/security-audit.md +11 -0
- package/kit/review/agents/uncle-bob.md +11 -0
- package/kit/review/skills/devops/SKILL.md +50 -0
- package/kit/review/skills/diagnose/SKILL.md +140 -0
- package/kit/review/skills/diagnose/references/feedback-loops.md +59 -0
- package/kit/review/skills/diagnose/scripts/hitl-loop.template.sh +71 -0
- package/kit/review/skills/pre-commit/SKILL.md +100 -0
- package/kit/review/skills/review/SKILL.md +43 -0
- package/kit/review/skills/security-audit/SKILL.md +65 -0
- package/kit/review/skills/uncle-bob/SKILL.md +43 -0
- package/kit/rules/rules/kit/arc42.md +89 -0
- package/kit/rules/rules/kit/architecture-files.md +87 -0
- package/kit/rules/rules/kit/code-style.md +159 -0
- package/kit/rules/rules/kit/pull-requests.md +59 -0
- package/kit/rules/rules/kit/testing.md +91 -0
- package/kit/rules/rules/kit/writing-for-agents.md +110 -0
- package/kit/workflow/agents/explore.md +25 -0
- package/kit/workflow/agents/implement.md +16 -0
- package/kit/workflow/agents/plan.md +13 -0
- package/kit/workflow/agents/spec-review.md +96 -0
- package/kit/workflow/skills/follow/SKILL.md +48 -0
- package/kit/workflow/skills/grill/SKILL.md +60 -0
- package/kit/workflow/skills/implement/SKILL.md +107 -0
- package/kit/workflow/skills/orchestrate/SKILL.md +565 -0
- package/kit/workflow/skills/orchestrate/handover/decision.ts +247 -0
- package/kit/workflow/skills/orchestrate/handover/index.ts +171 -0
- package/kit/workflow/skills/plan/SKILL.md +118 -0
- package/kit/workflow/skills/plan/references/template-English.md +92 -0
- package/kit/workflow/skills/plan/references/template-German.md +92 -0
- package/kit/workflow/skills/start/SKILL.md +47 -0
- package/package.json +63 -5
- package/schemas/jackherd.schema.json +601 -0
- package/src/check/changes.ts +45 -0
- package/src/check/command-line.ts +12 -0
- package/src/check/command.ts +136 -0
- package/src/check/deferral-arrows.ts +41 -0
- package/src/check/execute.ts +29 -0
- package/src/check/glob.ts +15 -0
- package/src/check/plan-structure-check.ts +135 -0
- package/src/check/plan.ts +106 -0
- package/src/check/public-content-check.ts +32 -0
- package/src/check/public-content-facts.ts +177 -0
- package/src/check/public-content.ts +216 -0
- package/src/check/report.ts +55 -0
- package/src/check/result.ts +42 -0
- package/src/check/roots.ts +36 -0
- package/src/check/routing.ts +22 -0
- package/src/check/schema.ts +83 -0
- package/src/check/settings.ts +44 -0
- package/src/claude/paths.ts +14 -0
- package/src/cli.ts +46 -0
- package/src/config/command.ts +45 -0
- package/src/config/detect.ts +196 -0
- package/src/config/index.ts +2 -0
- package/src/config/json-schema.ts +5 -0
- package/src/config/legacy-names.ts +56 -0
- package/src/config/link-kinds.ts +25 -0
- package/src/config/location.ts +46 -0
- package/src/config/read-config.ts +42 -0
- package/src/config/repository-facts.ts +19 -0
- package/src/config/repository-values.ts +46 -0
- package/src/config/schema.ts +302 -0
- package/src/context/command.ts +39 -0
- package/src/context/coordinator-name.ts +16 -0
- package/src/context/fill.ts +69 -0
- package/src/context/index.ts +10 -0
- package/src/context/level.ts +22 -0
- package/src/context/options.ts +29 -0
- package/src/context/report.ts +42 -0
- package/src/context/session-log.ts +91 -0
- package/src/plan-format/drift.ts +69 -0
- package/src/plan-format/header.ts +27 -0
- package/src/plan-format/index.ts +9 -0
- package/src/plan-format/lanes.ts +128 -0
- package/src/plan-format/markdown.ts +98 -0
- package/src/plan-format/phase-tally.ts +55 -0
- package/src/plan-format/phases.ts +224 -0
- package/src/plan-format/plan-structure.ts +150 -0
- package/src/plan-format/progress.ts +202 -0
- package/src/plan-format/sprint-document.ts +69 -0
- package/src/plan-format/terminal-text.ts +28 -0
- package/src/plans/board-choice.ts +60 -0
- package/src/plans/board-pick.ts +72 -0
- package/src/plans/board-rows.ts +62 -0
- package/src/plans/command.ts +60 -0
- package/src/plans/ensure-panes.ts +71 -0
- package/src/plans/error-text.ts +5 -0
- package/src/plans/fetch.ts +96 -0
- package/src/plans/git.ts +145 -0
- package/src/plans/github.ts +35 -0
- package/src/plans/lane-discovery.ts +34 -0
- package/src/plans/lane-plans.ts +80 -0
- package/src/plans/layout.ts +881 -0
- package/src/plans/link-chips.ts +74 -0
- package/src/plans/live.ts +336 -0
- package/src/plans/markdown-html.ts +140 -0
- package/src/plans/options.ts +15 -0
- package/src/plans/phase-source.ts +71 -0
- package/src/plans/plan-chips.ts +75 -0
- package/src/plans/plan-panes.ts +206 -0
- package/src/plans/planned.ts +117 -0
- package/src/plans/pull-requests.ts +107 -0
- package/src/plans/restart-decision.ts +42 -0
- package/src/plans/selection.ts +352 -0
- package/src/plans/settings.ts +68 -0
- package/src/plans/single-flight.ts +25 -0
- package/src/plans/snapshot.ts +300 -0
- package/src/plans/sprint-export.ts +103 -0
- package/src/plans/sprint-html.ts +301 -0
- package/src/plans/sprint-page.ts +287 -0
- package/src/plans/supervisor.ts +124 -0
- package/src/plans/symbols.ts +54 -0
- package/src/plans/texts.ts +103 -0
- package/src/plans/unlisted-lanes.ts +45 -0
- package/src/plans/view.ts +68 -0
- package/src/plans/worked-phases.ts +53 -0
- package/src/sync/checksum.ts +13 -0
- package/src/sync/choice.ts +14 -0
- package/src/sync/command-context.ts +113 -0
- package/src/sync/command.ts +94 -0
- package/src/sync/error-message.ts +1 -0
- package/src/sync/gitignore.ts +45 -0
- package/src/sync/global-kit.ts +176 -0
- package/src/sync/herdr-plugin.ts +175 -0
- package/src/sync/if-changed.ts +109 -0
- package/src/sync/init-command.ts +239 -0
- package/src/sync/init.ts +136 -0
- package/src/sync/kit-hooks.ts +18 -0
- package/src/sync/kit-path.ts +77 -0
- package/src/sync/kit-source.ts +169 -0
- package/src/sync/kit.ts +9 -0
- package/src/sync/legacy-files.ts +116 -0
- package/src/sync/manifest.ts +44 -0
- package/src/sync/next-steps.ts +42 -0
- package/src/sync/plan-file.ts +154 -0
- package/src/sync/plan-hooks.ts +165 -0
- package/src/sync/project-fs.ts +132 -0
- package/src/sync/render.ts +60 -0
- package/src/sync/report.ts +96 -0
- package/src/sync/settings-hooks.ts +90 -0
- package/src/sync/settings-migration.ts +94 -0
- package/src/sync/settings-offer.ts +168 -0
- package/src/sync/setup-questions.ts +70 -0
- package/src/sync/setup.ts +52 -0
- package/src/sync/sync-plan.ts +120 -0
- package/src/sync/sync-result.ts +9 -0
- package/src/sync/sync.ts +307 -0
- package/src/sync/tools.ts +52 -0
- package/src/terminal/herdr.ts +91 -0
- package/src/terminal/index.ts +13 -0
- package/src/terminal/none.ts +7 -0
- package/src/terminal/open.ts +39 -0
- package/src/terminal/text.ts +159 -0
- package/src/terminal/types.ts +30 -0
- package/src/terminal/width.ts +90 -0
- package/src/update/command.ts +65 -0
- package/src/update/package-manager.ts +56 -0
- package/src/update/update.ts +105 -0
- package/src/version/installed-version.ts +32 -0
- package/src/version/release.ts +22 -0
- package/src/version/skill-diff.ts +63 -0
- package/src/version/started-kit.ts +14 -0
- package/src/vocabulary/index.ts +132 -0
- package/src/watch/base-tip.ts +18 -0
- package/src/watch/command.ts +919 -0
- package/src/watch/coordinator-fill.ts +76 -0
- package/src/watch/feedback-read.ts +73 -0
- package/src/watch/feedback.ts +308 -0
- package/src/watch/gh.ts +22 -0
- package/src/watch/github-read.ts +175 -0
- package/src/watch/herdr.ts +11 -0
- package/src/watch/json-guards.ts +9 -0
- package/src/watch/kit-update.ts +18 -0
- package/src/watch/lane-fill.ts +36 -0
- package/src/watch/lane-message.ts +99 -0
- package/src/watch/lane-progress.ts +44 -0
- package/src/watch/lane-report-file.ts +53 -0
- package/src/watch/lane-report.ts +270 -0
- package/src/watch/lane-search.ts +41 -0
- package/src/watch/lane-sources.ts +46 -0
- package/src/watch/lane-watch.ts +121 -0
- package/src/watch/monitor-tool.ts +19 -0
- package/src/watch/options.ts +79 -0
- package/src/watch/plan-badges-read.ts +30 -0
- package/src/watch/plan-badges.ts +66 -0
- package/src/watch/plan-views.ts +59 -0
- package/src/watch/preview-read.ts +60 -0
- package/src/watch/previews.ts +234 -0
- package/src/watch/pull-requests.ts +117 -0
- package/src/watch/single-report.ts +15 -0
- package/src/watch/skill-change.ts +61 -0
- package/src/watch/source-dry-run-command.ts +35 -0
- package/src/watch/source-dry-run.ts +124 -0
- package/src/watch/source-fields.ts +9 -0
- package/src/watch/source-path.ts +40 -0
- package/src/watch/source-run.ts +90 -0
- package/src/watch/sources.ts +344 -0
- package/src/watch/stream-output.ts +14 -0
- package/src/watch/texts.ts +76 -0
- package/src/watch/watch-events.ts +399 -0
- package/src/watch/watcher-lock.ts +145 -0
- package/src/watch/workflow-runs.ts +96 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
id = "crossdelta.jackherd"
|
|
2
|
+
name = "jackherd"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
min_herdr_version = "0.8.2"
|
|
5
|
+
description = "Show the plans of a board's lanes in a herdr pane"
|
|
6
|
+
platforms = ["linux", "macos"]
|
|
7
|
+
|
|
8
|
+
[[panes]]
|
|
9
|
+
id = "plans"
|
|
10
|
+
title = "jackherd plans"
|
|
11
|
+
placement = "split"
|
|
12
|
+
command = ["bunx", "jackherd", "plans"]
|
|
13
|
+
|
|
14
|
+
[[actions]]
|
|
15
|
+
id = "open-plans"
|
|
16
|
+
title = "Open the plans view"
|
|
17
|
+
description = "Split the focused pane to the right and run jh plans there, in the focused pane's directory"
|
|
18
|
+
contexts = ["workspace"]
|
|
19
|
+
command = ["bun", "open-plans.ts"]
|
|
20
|
+
|
|
21
|
+
[[startup]]
|
|
22
|
+
command = ["bun", "restore-plans.ts"]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
export const PLUGIN_ID = 'crossdelta.jackherd'
|
|
4
|
+
|
|
5
|
+
export const PLANS_PANE = 'plans'
|
|
6
|
+
|
|
7
|
+
/** The directory herdr reports for a pane in the JSON answer of `herdr pane get`, null when it names none. */
|
|
8
|
+
export const paneDirectoryOf = (json: string): string | null => {
|
|
9
|
+
try {
|
|
10
|
+
const cwd = (JSON.parse(json) as { result?: { pane?: { cwd?: unknown } } }).result?.pane?.cwd
|
|
11
|
+
return typeof cwd === 'string' && cwd !== '' ? cwd : null
|
|
12
|
+
} catch {
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Arguments of `herdr plugin pane open` for the plans pane: to the right of the pane, in its directory when known. */
|
|
18
|
+
export const openPlansArguments = (pane: string | null, directory: string | null): string[] => [
|
|
19
|
+
'plugin',
|
|
20
|
+
'pane',
|
|
21
|
+
'open',
|
|
22
|
+
'--plugin',
|
|
23
|
+
PLUGIN_ID,
|
|
24
|
+
'--entrypoint',
|
|
25
|
+
PLANS_PANE,
|
|
26
|
+
'--placement',
|
|
27
|
+
'split',
|
|
28
|
+
'--direction',
|
|
29
|
+
'right',
|
|
30
|
+
...(pane ? ['--target-pane', pane] : []),
|
|
31
|
+
...(directory ? ['--cwd', directory] : []),
|
|
32
|
+
'--focus',
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
const main = (): number => {
|
|
36
|
+
const herdr = process.env.HERDR_BIN_PATH || 'herdr'
|
|
37
|
+
const pane = process.env.HERDR_PANE_ID || null
|
|
38
|
+
const answer = pane ? spawnSync(herdr, ['pane', 'get', pane], { encoding: 'utf-8' }) : null
|
|
39
|
+
const directory = answer?.status === 0 ? paneDirectoryOf(answer.stdout) : null
|
|
40
|
+
return spawnSync(herdr, openPlansArguments(pane, directory), { stdio: 'inherit' }).status ?? 1
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (import.meta.main) process.exitCode = main()
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
type Limits = { attempts: number; intervalMs: number }
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Measured on herdr 0.9.0: the hook starts ~85 ms after the server, `pane process-info` answers
|
|
8
|
+
* `pane_not_found` until the terminals spawn at ~0.9 s, and the agents are back at ~3 s. The panes get 10 s, ten
|
|
9
|
+
* times what they took; `plans --ensure` gets four tries 2 s apart, past the ~3 s of the agents, for a pane that
|
|
10
|
+
* goes away between the two reads.
|
|
11
|
+
*/
|
|
12
|
+
export const RESTORE_LIMITS = {
|
|
13
|
+
panes: { attempts: 20, intervalMs: 500 },
|
|
14
|
+
ensure: { attempts: 4, intervalMs: 2_000 },
|
|
15
|
+
} as const satisfies Record<string, Limits>
|
|
16
|
+
|
|
17
|
+
/** Runs `attempt` until it works or the attempts are used up, waiting the interval between two of them. */
|
|
18
|
+
export const retried = (attempt: () => boolean, limits: Limits, sleep: (ms: number) => void): boolean => {
|
|
19
|
+
for (let tried = 1; ; tried++) {
|
|
20
|
+
if (attempt()) return true
|
|
21
|
+
if (tried >= limits.attempts) return false
|
|
22
|
+
sleep(limits.intervalMs)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The directories of the panes an agent runs in, each once, from a `herdr pane list` answer; none when unreadable.
|
|
28
|
+
* `agentPanes` are the panes of `herdr agent list`: right after a restart the pane list names no agent there for a
|
|
29
|
+
* few seconds.
|
|
30
|
+
*/
|
|
31
|
+
export const agentDirectoriesOf = (json: string, agentPanes: readonly string[]): string[] => {
|
|
32
|
+
try {
|
|
33
|
+
const panes = (JSON.parse(json) as { result?: { panes?: unknown } }).result?.panes
|
|
34
|
+
if (!Array.isArray(panes)) return []
|
|
35
|
+
const directories = panes.flatMap((pane) => {
|
|
36
|
+
const { cwd, agent, pane_id } = (pane ?? {}) as { cwd?: unknown; agent?: unknown; pane_id?: unknown }
|
|
37
|
+
const hasAgent = typeof agent === 'string' || (typeof pane_id === 'string' && agentPanes.includes(pane_id))
|
|
38
|
+
return typeof cwd === 'string' && hasAgent ? [cwd] : []
|
|
39
|
+
})
|
|
40
|
+
return [...new Set(directories)]
|
|
41
|
+
} catch {
|
|
42
|
+
return []
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The arguments for `bun` that run `plans --ensure` with the CLI of the package this plugin ships in. */
|
|
47
|
+
export const ensureArguments = (pluginRoot: string): string[] => [
|
|
48
|
+
join(dirname(pluginRoot), 'src', 'cli.ts'),
|
|
49
|
+
'plans',
|
|
50
|
+
'--ensure',
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
const idsOf = (json: string, key: 'panes' | 'agents'): string[] => {
|
|
54
|
+
try {
|
|
55
|
+
const entries = (JSON.parse(json) as { result?: Record<string, unknown> }).result?.[key]
|
|
56
|
+
if (!Array.isArray(entries)) return []
|
|
57
|
+
return entries.flatMap((entry) => {
|
|
58
|
+
const id = (entry as { pane_id?: unknown } | null)?.pane_id
|
|
59
|
+
return typeof id === 'string' ? [id] : []
|
|
60
|
+
})
|
|
61
|
+
} catch {
|
|
62
|
+
return []
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const mainCheckoutOf = (directory: string): string | null => {
|
|
67
|
+
const answer = spawnSync('git', ['-C', directory, 'rev-parse', '--path-format=absolute', '--git-common-dir'], {
|
|
68
|
+
encoding: 'utf-8',
|
|
69
|
+
})
|
|
70
|
+
return answer.status === 0 ? dirname(answer.stdout.trim()) : null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const herdrRun = (args: string[]) =>
|
|
74
|
+
spawnSync(process.env.HERDR_BIN_PATH || 'herdr', args, { encoding: 'utf-8' })
|
|
75
|
+
|
|
76
|
+
/** Whether herdr answers `process-info` for every pane, which it does only once their terminals run. */
|
|
77
|
+
const panesAnswer = (panes: readonly string[]): boolean =>
|
|
78
|
+
panes.every((pane) => herdrRun(['pane', 'process-info', '--pane', pane]).status === 0)
|
|
79
|
+
|
|
80
|
+
const ensureIn = (root: string, pluginRoot: string): boolean =>
|
|
81
|
+
spawnSync('bun', ensureArguments(pluginRoot), { cwd: root, stdio: 'inherit' }).status === 0
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* herdr's `[[startup]]` hook: after a server start or a live handoff, brings back the plans views of every
|
|
85
|
+
* repository an agent runs in. It parses herdr's answers itself and never throws, unlike the strict parsers of
|
|
86
|
+
* `src/plans/plan-panes.ts`: a hook that fails leaves nothing behind but a log line, and `plans --ensure`, which it
|
|
87
|
+
* calls, reads the panes again strictly. It reads the panes at once, while herdr still names their agents, then waits until
|
|
88
|
+
* herdr answers for every pane and retries `plans --ensure` within `RESTORE_LIMITS`. A repository without a board
|
|
89
|
+
* or config only prints why and is skipped.
|
|
90
|
+
*/
|
|
91
|
+
const main = (): number => {
|
|
92
|
+
const list = herdrRun(['pane', 'list'])
|
|
93
|
+
if (list.status !== 0) console.error(`restore-plans: herdr pane list failed: ${list.stderr || list.status}`)
|
|
94
|
+
const listing = list.stdout ?? ''
|
|
95
|
+
const directories = agentDirectoriesOf(listing, idsOf(herdrRun(['agent', 'list']).stdout ?? '', 'agents'))
|
|
96
|
+
const roots = new Set(directories.flatMap((directory) => mainCheckoutOf(directory) ?? []))
|
|
97
|
+
if (roots.size === 0) return 0
|
|
98
|
+
const sleep = (ms: number) => Bun.sleepSync(ms)
|
|
99
|
+
if (!retried(() => panesAnswer(idsOf(listing, 'panes')), RESTORE_LIMITS.panes, sleep))
|
|
100
|
+
console.error('restore-plans: herdr did not answer for every pane in time, trying anyway')
|
|
101
|
+
for (const root of roots)
|
|
102
|
+
if (!retried(() => ensureIn(root, import.meta.dir), RESTORE_LIMITS.ensure, sleep))
|
|
103
|
+
console.error(`restore-plans: plans --ensure failed in ${root}`)
|
|
104
|
+
return 0
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (import.meta.main) process.exitCode = main()
|
package/install.sh
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Installs jackherd for Claude Code: Bun only if it is missing, then the kit, then its setup wizard.
|
|
3
|
+
#
|
|
4
|
+
# curl -fsSL https://unpkg.com/jackherd/install.sh | sh
|
|
5
|
+
#
|
|
6
|
+
# Never as root, no edits to shell files: the official Bun installer puts Bun into ~/.bun and says
|
|
7
|
+
# itself which shell file it changed. Every action is announced on one line before it runs.
|
|
8
|
+
set -eu
|
|
9
|
+
|
|
10
|
+
# The default is the version this file ships with. The release bump changes it together with
|
|
11
|
+
# package.json; install.test.ts fails while the two differ. Overridable, but only by a plain release.
|
|
12
|
+
JACKHERD_VERSION="${JACKHERD_VERSION:-1.0.0}"
|
|
13
|
+
|
|
14
|
+
JACKHERD_PACKAGE='jackherd'
|
|
15
|
+
# Test seam: the terminal the wizard asks its questions on.
|
|
16
|
+
JACKHERD_TEST_TTY="${JACKHERD_TEST_TTY:-/dev/tty}"
|
|
17
|
+
BUN_INSTALLER_URL='https://bun.com/install'
|
|
18
|
+
BUN_MIN_MAJOR=1
|
|
19
|
+
BUN_MIN_MINOR=3
|
|
20
|
+
|
|
21
|
+
say() {
|
|
22
|
+
printf 'jackherd install: %s\n' "$1"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fail() {
|
|
26
|
+
printf 'jackherd install: %s\n' "$1" >&2
|
|
27
|
+
exit 1
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Digits and exactly two inner dots: major.minor.patch, nothing a shell or npm could read differently.
|
|
31
|
+
is_release() {
|
|
32
|
+
case $1 in
|
|
33
|
+
'' | *[!0-9.]* | .* | *. | *..* | *.*.*.*) return 1 ;;
|
|
34
|
+
*.*.*) return 0 ;;
|
|
35
|
+
*) return 1 ;;
|
|
36
|
+
esac
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
is_number() {
|
|
40
|
+
case $1 in
|
|
41
|
+
'' | *[!0-9]*) return 1 ;;
|
|
42
|
+
*) return 0 ;;
|
|
43
|
+
esac
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
check_platform() {
|
|
47
|
+
os=$(uname -s)
|
|
48
|
+
case $os in
|
|
49
|
+
Linux | Darwin) ;;
|
|
50
|
+
*) fail "unsupported system '$os': this installer runs on Linux and macOS only" ;;
|
|
51
|
+
esac
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
bun_bin_dir() {
|
|
55
|
+
printf '%s/bin\n' "${BUN_INSTALL:-$HOME/.bun}"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# The bun on PATH, else the one the official installer leaves in ~/.bun/bin; empty when there is none.
|
|
59
|
+
find_bun() {
|
|
60
|
+
if command -v bun >/dev/null 2>&1; then
|
|
61
|
+
command -v bun
|
|
62
|
+
elif [ -x "$(bun_bin_dir)/bun" ]; then
|
|
63
|
+
printf '%s\n' "$(bun_bin_dir)/bun"
|
|
64
|
+
fi
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
require_bun_installer_tools() {
|
|
68
|
+
missing=''
|
|
69
|
+
for tool in curl unzip bash; do
|
|
70
|
+
command -v "$tool" >/dev/null 2>&1 || missing="$missing $tool"
|
|
71
|
+
done
|
|
72
|
+
[ -z "$missing" ] || fail "Bun is missing, and installing it needs:$missing. Install them and run this again."
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
install_bun() {
|
|
76
|
+
require_bun_installer_tools
|
|
77
|
+
installer=$(mktemp) || fail "could not create a temporary file"
|
|
78
|
+
trap 'rm -f "$installer"' EXIT
|
|
79
|
+
# A trapped signal would otherwise let the script carry on; exiting runs the EXIT trap above.
|
|
80
|
+
trap 'exit 1' INT TERM HUP
|
|
81
|
+
say "Bun not found, downloading the official installer from $BUN_INSTALLER_URL"
|
|
82
|
+
curl --proto '=https' --tlsv1.2 -fsSL "$BUN_INSTALLER_URL" -o "$installer" || fail "could not download $BUN_INSTALLER_URL"
|
|
83
|
+
say "running the Bun installer (installs into $(bun_bin_dir), without root)"
|
|
84
|
+
bash "$installer" || fail "the Bun installer failed"
|
|
85
|
+
[ -x "$(bun_bin_dir)/bun" ] || fail "the Bun installer finished, but $(bun_bin_dir)/bun is missing"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
check_bun_version() {
|
|
89
|
+
version=$("$1" --version) || fail "could not run '$1 --version'"
|
|
90
|
+
major=${version%%.*}
|
|
91
|
+
rest=${version#*.}
|
|
92
|
+
minor=${rest%%.*}
|
|
93
|
+
if ! is_number "$major" || ! is_number "$minor"; then
|
|
94
|
+
fail "could not read the Bun version '$version'"
|
|
95
|
+
fi
|
|
96
|
+
if [ "$major" -lt "$BUN_MIN_MAJOR" ] || { [ "$major" -eq "$BUN_MIN_MAJOR" ] && [ "$minor" -lt "$BUN_MIN_MINOR" ]; }; then
|
|
97
|
+
fail "Bun $version is too old, jackherd needs $BUN_MIN_MAJOR.$BUN_MIN_MINOR or newer. Update it with 'bun upgrade' and run this again."
|
|
98
|
+
fi
|
|
99
|
+
say "using Bun $version ($1)"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Bun's global bin, not whatever jh comes first on PATH: an older install elsewhere stays untouched.
|
|
103
|
+
jh_bin() {
|
|
104
|
+
global_bin=$("$1" pm bin -g) || fail "could not read Bun's global bin ('bun pm bin -g')"
|
|
105
|
+
[ -x "$global_bin/jh" ] || fail "jh is not in Bun's global bin ($global_bin) after the install"
|
|
106
|
+
printf '%s\n' "$global_bin/jh"
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# Opening it is the test: under 'curl | sh' stdin is the script, and /dev/tty may exist without a terminal.
|
|
110
|
+
terminal_available() {
|
|
111
|
+
(: <"$JACKHERD_TEST_TTY") 2>/dev/null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
run_setup() {
|
|
115
|
+
if terminal_available; then
|
|
116
|
+
say "starting the setup: jh init --global"
|
|
117
|
+
"$1" init --global <"$JACKHERD_TEST_TTY" || fail "jh init --global failed"
|
|
118
|
+
else
|
|
119
|
+
say "no terminal to ask questions on, setting up with the defaults: jh init --global --yes"
|
|
120
|
+
"$1" init --global --yes </dev/null || fail "jh init --global --yes failed"
|
|
121
|
+
fi
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
main() {
|
|
125
|
+
[ "$(id -u)" -ne 0 ] || fail "run this as your own user, not as root (no sudo)"
|
|
126
|
+
is_release "$JACKHERD_VERSION" || fail "JACKHERD_VERSION must be a plain release like 1.2.3, not '$JACKHERD_VERSION'"
|
|
127
|
+
[ -n "${HOME:-}" ] || fail "HOME is not set"
|
|
128
|
+
check_platform
|
|
129
|
+
# Bun reads bunfig.toml (registry, preload, global bin) from the working directory; one in the
|
|
130
|
+
# directory this was started from must not steer the install. 'init --global' writes to ~ only.
|
|
131
|
+
cd "$HOME" || fail "could not change into HOME ($HOME)"
|
|
132
|
+
|
|
133
|
+
bun=$(find_bun)
|
|
134
|
+
if [ -z "$bun" ]; then
|
|
135
|
+
install_bun
|
|
136
|
+
bun="$(bun_bin_dir)/bun"
|
|
137
|
+
fi
|
|
138
|
+
case $bun in
|
|
139
|
+
/*) ;;
|
|
140
|
+
*) fail "found bun at '$bun', expected an absolute path" ;;
|
|
141
|
+
esac
|
|
142
|
+
check_bun_version "$bun"
|
|
143
|
+
# This run only: the kit's CLI starts with '#!/usr/bin/env bun'.
|
|
144
|
+
PATH="$(dirname "$bun"):$PATH"
|
|
145
|
+
export PATH
|
|
146
|
+
|
|
147
|
+
spec="$JACKHERD_PACKAGE@$JACKHERD_VERSION"
|
|
148
|
+
say "installing $spec: bun add -g --ignore-scripts $spec"
|
|
149
|
+
"$bun" add -g --ignore-scripts "$spec" || fail "bun add -g failed"
|
|
150
|
+
|
|
151
|
+
# An assignment, so a failure inside the substitution stops the run (set -e) instead of passing on ''.
|
|
152
|
+
jh=$(jh_bin "$bun")
|
|
153
|
+
run_setup "$jh"
|
|
154
|
+
say "done"
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
main
|