jackherd 1.0.1 → 1.0.3

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 CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  What changed for people who use jackherd, newest first.
4
4
 
5
+ ## 1.0.3
6
+
7
+ - **Update through the old command.** `agent-kit update`, run after moving from `@crossdelta/agent-kit`, now installs `jh` globally. Before, it found the bundled copy already current and installed nothing, so `jh` stayed off your path.
8
+
9
+ ## 1.0.2
10
+
11
+ - **npm.** The package carries keywords, so a search on npm finds it.
12
+
5
13
  ## 1.0.1
6
14
 
7
15
  - **README.** Jack's introduction no longer names an author or repeats his name, and the logo has some room above the text.
@@ -1,6 +1,6 @@
1
1
  id = "crossdelta.jackherd"
2
2
  name = "jackherd"
3
- version = "1.0.1"
3
+ version = "1.0.3"
4
4
  min_herdr_version = "0.8.2"
5
5
  description = "Show the plans of a board's lanes in a herdr pane"
6
6
  platforms = ["linux", "macos"]
package/install.sh CHANGED
@@ -9,7 +9,7 @@ set -eu
9
9
 
10
10
  # The default is the version this file ships with. The release bump changes it together with
11
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.1}"
12
+ JACKHERD_VERSION="${JACKHERD_VERSION:-1.0.3}"
13
13
 
14
14
  JACKHERD_PACKAGE='jackherd'
15
15
  # Test seam: the terminal the wizard asks its questions on.
package/package.json CHANGED
@@ -1,6 +1,18 @@
1
1
  {
2
2
  "name": "jackherd",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
+ "keywords": [
5
+ "claude-code",
6
+ "coding-agents",
7
+ "ai-agents",
8
+ "agent-skills",
9
+ "subagents",
10
+ "orchestration",
11
+ "git-worktree",
12
+ "herdr",
13
+ "planning",
14
+ "cli"
15
+ ],
4
16
  "description": "Skills, agents and plan tooling for Claude Code projects",
5
17
  "author": "crossdelta",
6
18
  "license": "MIT",
@@ -13,7 +13,7 @@ import { defaultUpdateCheck, lockedVersion } from '../sync/if-changed'
13
13
  import { locateKit } from '../sync/kit-source'
14
14
  import { readInstalledVersion } from '../version/installed-version'
15
15
  import { CLI_NAME } from '../version/release'
16
- import { detectPackageManager, legacyPackageRoot } from './package-manager'
16
+ import { detectPackageManager, legacyPackageRoot, shellHostedGlobalRoot } from './package-manager'
17
17
  import { runUpdate, UPDATE_EXIT, type UpdateAdapters } from './update'
18
18
 
19
19
  const USAGE = `Usage: ${CLI_NAME} update`
@@ -36,15 +36,18 @@ const runInherited = async (argv: readonly string[], cwd: string): Promise<numbe
36
36
  const adaptersFor = async (): Promise<UpdateAdapters> => {
37
37
  const { version, packageRoot } = await locateKit()
38
38
  const home = homedir()
39
+ const realRoot = realpathSync(packageRoot)
40
+ const targetRoot = shellHostedGlobalRoot(realRoot) ?? realRoot
39
41
  return {
40
42
  installedVersion: version,
41
- manager: detectPackageManager(realpathSync(packageRoot)),
43
+ manager: detectPackageManager(realRoot),
42
44
  latestVersion: defaultUpdateCheck().latestVersion,
43
- installedVersionOnDisk: () => readInstalledVersion(packageRoot),
44
- kitCli: [process.execPath, join(packageRoot, 'src', 'cli.ts')],
45
+ installedVersionOnDisk: () => readInstalledVersion(targetRoot),
46
+ kitCli: [process.execPath, join(targetRoot, 'src', 'cli.ts')],
45
47
  run: runInherited,
46
48
  hasGlobalKit: async () => (await lockedVersion(home)) !== null,
47
- hasLegacyPackage: () => existsSync(legacyPackageRoot(realpathSync(packageRoot))),
49
+ hasLegacyPackage: () => existsSync(legacyPackageRoot(targetRoot)),
50
+ jhOnPath: () => Bun.which(CLI_NAME) !== null,
48
51
  home,
49
52
  projectRoot: findProjectRoot(process.cwd(), home, existsSync),
50
53
  log: (line) => console.log(line),
@@ -25,6 +25,17 @@ export const detectPackageManager = (packageRoot: string): PackageManager | null
25
25
  return GLOBAL_LOCATIONS.find(([pattern]) => pattern.test(path))?.[1] ?? null
26
26
  }
27
27
 
28
+ const SHELL_DEPENDENCY = `/${LEGACY_PACKAGE_NAME}/node_modules/${PACKAGE_NAME}`
29
+
30
+ /**
31
+ * Where a global jackherd lies when this one sits nested inside the `@crossdelta/agent-kit` shell, as npm lays
32
+ * it out (beside the shell, in the same global `node_modules`); null for any other layout, bun's flat one included.
33
+ */
34
+ export const shellHostedGlobalRoot = (packageRoot: string): string | null => {
35
+ const path = packageRoot.replaceAll('\\', '/')
36
+ return path.endsWith(SHELL_DEPENDENCY) ? `${path.slice(0, -SHELL_DEPENDENCY.length)}/${PACKAGE_NAME}` : null
37
+ }
38
+
28
39
  /**
29
40
  * The argv, never a shell line, that installs `version` globally without running install scripts: the kit
30
41
  * has none and a dependency's would run before the new code is looked at. Throws unless `version` is a plain release.
@@ -18,6 +18,8 @@ export type UpdateAdapters = {
18
18
  hasGlobalKit: () => Promise<boolean>
19
19
  /** True when the package before the rename is installed globally beside this one. */
20
20
  hasLegacyPackage: () => boolean
21
+ /** Whether a `jh` command is on the path; false when only the `@crossdelta/agent-kit` shell carries this jackherd. Asked before and after the install. */
22
+ jhOnPath: () => boolean
21
23
  home: string
22
24
  /** The project the working directory belongs to, or null. */
23
25
  projectRoot: string | null
@@ -57,10 +59,16 @@ const installAndVerify = async (
57
59
  return UPDATE_EXIT.error
58
60
  }
59
61
 
62
+ const doneLine = (adapters: UpdateAdapters, manager: PackageManager, latest: string, jhWasOnPath: boolean): string => {
63
+ if (jhWasOnPath) return `${CLI_NAME} updated ${adapters.installedVersion} → ${latest}.`
64
+ if (adapters.jhOnPath()) return `${CLI_NAME} ${latest} installed globally.`
65
+ return `${CLI_NAME} ${latest} installed, but no ${CLI_NAME} is on your path yet: add the global bin directory of ${manager} to it.`
66
+ }
67
+
60
68
  /**
61
69
  * The update itself: installs the newest release, then runs the syncs with the freshly installed code. Does
62
- * nothing when no newer release exists. The version it
63
- * installs is the one it checked, and only a plain release reaches the command line.
70
+ * nothing when no newer release exists and `jh` is on the path; without `jh` it installs even the running
71
+ * version. The version it installs is the one it checked, and only a plain release reaches the command line.
64
72
  */
65
73
  const updateToLatest = async (adapters: UpdateAdapters, manager: PackageManager): Promise<number> => {
66
74
  const { installedVersion, log } = adapters
@@ -73,7 +81,8 @@ const updateToLatest = async (adapters: UpdateAdapters, manager: PackageManager)
73
81
  log(`${CLI_NAME} update: the npm registry answered no plain release version, nothing was changed.`)
74
82
  return UPDATE_EXIT.error
75
83
  }
76
- if (!isNewerVersion(latest, installedVersion)) {
84
+ const jhWasOnPath = adapters.jhOnPath()
85
+ if (jhWasOnPath && !isNewerVersion(latest, installedVersion)) {
77
86
  log(`${CLI_NAME} ${installedVersion} is the latest release.`)
78
87
  return UPDATE_EXIT.ok
79
88
  }
@@ -82,7 +91,7 @@ const updateToLatest = async (adapters: UpdateAdapters, manager: PackageManager)
82
91
  for (const { argv, cwd } of await syncSteps(adapters)) {
83
92
  if ((await adapters.run(argv, cwd)) !== 0) return failed(log, 'the sync', argv.join(' '))
84
93
  }
85
- log(`${CLI_NAME} updated ${installedVersion} → ${latest}.`)
94
+ log(doneLine(adapters, manager, latest, jhWasOnPath))
86
95
  return UPDATE_EXIT.ok
87
96
  }
88
97