@halyard/cli 0.1.2 → 0.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 (141) hide show
  1. package/README.md +88 -1
  2. package/dist/cli.js +27 -4
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/api-keys.d.ts +3 -0
  5. package/dist/commands/api-keys.d.ts.map +1 -0
  6. package/dist/commands/api-keys.js +90 -0
  7. package/dist/commands/api-keys.js.map +1 -0
  8. package/dist/commands/hook.d.ts +6 -0
  9. package/dist/commands/hook.d.ts.map +1 -0
  10. package/dist/commands/hook.js +106 -0
  11. package/dist/commands/hook.js.map +1 -0
  12. package/dist/commands/login.d.ts +17 -0
  13. package/dist/commands/login.d.ts.map +1 -1
  14. package/dist/commands/login.js +111 -14
  15. package/dist/commands/login.js.map +1 -1
  16. package/dist/commands/orgs.d.ts +3 -0
  17. package/dist/commands/orgs.d.ts.map +1 -0
  18. package/dist/commands/orgs.js +102 -0
  19. package/dist/commands/orgs.js.map +1 -0
  20. package/dist/commands/push.d.ts.map +1 -1
  21. package/dist/commands/push.js +29 -6
  22. package/dist/commands/push.js.map +1 -1
  23. package/dist/commands/sessions.d.ts +3 -0
  24. package/dist/commands/sessions.d.ts.map +1 -0
  25. package/dist/commands/sessions.js +139 -0
  26. package/dist/commands/sessions.js.map +1 -0
  27. package/dist/commands/setup.d.ts.map +1 -1
  28. package/dist/commands/setup.js +28 -3
  29. package/dist/commands/setup.js.map +1 -1
  30. package/dist/commands/skills.d.ts +12 -0
  31. package/dist/commands/skills.d.ts.map +1 -0
  32. package/dist/commands/skills.js +171 -0
  33. package/dist/commands/skills.js.map +1 -0
  34. package/dist/commands/sync.d.ts.map +1 -1
  35. package/dist/commands/sync.js +51 -49
  36. package/dist/commands/sync.js.map +1 -1
  37. package/dist/commands/users.d.ts +13 -0
  38. package/dist/commands/users.d.ts.map +1 -0
  39. package/dist/commands/users.js +205 -0
  40. package/dist/commands/users.js.map +1 -0
  41. package/dist/commands/whoami.d.ts +21 -0
  42. package/dist/commands/whoami.d.ts.map +1 -0
  43. package/dist/commands/whoami.js +77 -0
  44. package/dist/commands/whoami.js.map +1 -0
  45. package/dist/lib/api-client.d.ts +64 -2
  46. package/dist/lib/api-client.d.ts.map +1 -1
  47. package/dist/lib/api-client.js +32 -7
  48. package/dist/lib/api-client.js.map +1 -1
  49. package/dist/lib/auth.d.ts +42 -6
  50. package/dist/lib/auth.d.ts.map +1 -1
  51. package/dist/lib/auth.js +101 -27
  52. package/dist/lib/auth.js.map +1 -1
  53. package/dist/lib/config-files.d.ts +6 -0
  54. package/dist/lib/config-files.d.ts.map +1 -0
  55. package/dist/lib/config-files.js +27 -0
  56. package/dist/lib/config-files.js.map +1 -0
  57. package/dist/lib/config.d.ts +13 -7
  58. package/dist/lib/config.d.ts.map +1 -1
  59. package/dist/lib/config.js +22 -8
  60. package/dist/lib/config.js.map +1 -1
  61. package/dist/lib/copilot-export.d.ts +31 -0
  62. package/dist/lib/copilot-export.d.ts.map +1 -0
  63. package/dist/lib/copilot-export.js +129 -0
  64. package/dist/lib/copilot-export.js.map +1 -0
  65. package/dist/lib/discovery.d.ts +60 -2
  66. package/dist/lib/discovery.d.ts.map +1 -1
  67. package/dist/lib/discovery.js +187 -15
  68. package/dist/lib/discovery.js.map +1 -1
  69. package/dist/lib/hook-discovery.d.ts +43 -0
  70. package/dist/lib/hook-discovery.d.ts.map +1 -0
  71. package/dist/lib/hook-discovery.js +90 -0
  72. package/dist/lib/hook-discovery.js.map +1 -0
  73. package/dist/lib/hook-input.d.ts +36 -0
  74. package/dist/lib/hook-input.d.ts.map +1 -0
  75. package/dist/lib/hook-input.js +161 -0
  76. package/dist/lib/hook-input.js.map +1 -0
  77. package/dist/lib/hook-runner.d.ts +48 -0
  78. package/dist/lib/hook-runner.d.ts.map +1 -0
  79. package/dist/lib/hook-runner.js +306 -0
  80. package/dist/lib/hook-runner.js.map +1 -0
  81. package/dist/lib/http.d.ts +18 -0
  82. package/dist/lib/http.d.ts.map +1 -0
  83. package/dist/lib/http.js +54 -0
  84. package/dist/lib/http.js.map +1 -0
  85. package/dist/lib/opencode-export.d.ts +22 -0
  86. package/dist/lib/opencode-export.d.ts.map +1 -0
  87. package/dist/lib/opencode-export.js +53 -0
  88. package/dist/lib/opencode-export.js.map +1 -0
  89. package/dist/lib/output.d.ts +27 -0
  90. package/dist/lib/output.d.ts.map +1 -0
  91. package/dist/lib/output.js +74 -0
  92. package/dist/lib/output.js.map +1 -0
  93. package/dist/lib/preflight.d.ts +35 -0
  94. package/dist/lib/preflight.d.ts.map +1 -0
  95. package/dist/lib/preflight.js +54 -0
  96. package/dist/lib/preflight.js.map +1 -0
  97. package/dist/lib/repo-binding.d.ts +43 -0
  98. package/dist/lib/repo-binding.d.ts.map +1 -0
  99. package/dist/lib/repo-binding.js +158 -0
  100. package/dist/lib/repo-binding.js.map +1 -0
  101. package/dist/lib/repo-identity.d.ts +29 -0
  102. package/dist/lib/repo-identity.d.ts.map +1 -0
  103. package/dist/lib/repo-identity.js +113 -0
  104. package/dist/lib/repo-identity.js.map +1 -0
  105. package/dist/lib/setup-hooks.d.ts +41 -0
  106. package/dist/lib/setup-hooks.d.ts.map +1 -0
  107. package/dist/lib/setup-hooks.js +276 -0
  108. package/dist/lib/setup-hooks.js.map +1 -0
  109. package/dist/lib/setup.d.ts +4 -1
  110. package/dist/lib/setup.d.ts.map +1 -1
  111. package/dist/lib/setup.js +20 -31
  112. package/dist/lib/setup.js.map +1 -1
  113. package/dist/lib/skill-batches.d.ts +16 -0
  114. package/dist/lib/skill-batches.d.ts.map +1 -0
  115. package/dist/lib/skill-batches.js +35 -0
  116. package/dist/lib/skill-batches.js.map +1 -0
  117. package/dist/lib/skill-files.d.ts +4 -0
  118. package/dist/lib/skill-files.d.ts.map +1 -0
  119. package/dist/lib/skill-files.js +36 -0
  120. package/dist/lib/skill-files.js.map +1 -0
  121. package/dist/lib/skill-fingerprint.d.ts +55 -0
  122. package/dist/lib/skill-fingerprint.d.ts.map +1 -0
  123. package/dist/lib/skill-fingerprint.js +204 -0
  124. package/dist/lib/skill-fingerprint.js.map +1 -0
  125. package/dist/lib/skills-discovery.d.ts +57 -0
  126. package/dist/lib/skills-discovery.d.ts.map +1 -0
  127. package/dist/lib/skills-discovery.js +157 -0
  128. package/dist/lib/skills-discovery.js.map +1 -0
  129. package/dist/lib/skills-sync.d.ts +66 -0
  130. package/dist/lib/skills-sync.d.ts.map +1 -0
  131. package/dist/lib/skills-sync.js +251 -0
  132. package/dist/lib/skills-sync.js.map +1 -0
  133. package/dist/lib/sync-runner.d.ts +56 -0
  134. package/dist/lib/sync-runner.d.ts.map +1 -0
  135. package/dist/lib/sync-runner.js +106 -0
  136. package/dist/lib/sync-runner.js.map +1 -0
  137. package/dist/lib/sync-state.d.ts +36 -6
  138. package/dist/lib/sync-state.d.ts.map +1 -1
  139. package/dist/lib/sync-state.js +41 -11
  140. package/dist/lib/sync-state.js.map +1 -1
  141. package/package.json +10 -5
@@ -0,0 +1,29 @@
1
+ export interface RepoIdentity {
2
+ /** Absolute MAIN repo root (parent of the git common dir; worktrees collapse here). */
3
+ rootDir: string;
4
+ /** Normalized remote.origin.url, or null when the repo has no origin. */
5
+ remote: string | null;
6
+ }
7
+ /** Reset the per-process identity cache (tests). */
8
+ export declare function clearRepoIdentityCache(): void;
9
+ /**
10
+ * Resolve the repo identity for a directory: main-repo root via
11
+ * `git rev-parse --git-common-dir` plus the normalized origin remote.
12
+ * Nonexistent dirs and git failures resolve to null (unresolved).
13
+ * Results are cached per dir for the life of the process.
14
+ */
15
+ export declare function resolveRepoIdentity(dir: string): RepoIdentity | null;
16
+ /**
17
+ * Normalize a git remote URL so transport variants compare equal:
18
+ * `git@github.com:ajust-au/agent.git` === `https://github.com/ajust-au/agent`.
19
+ * Lowercases the host, strips protocol/credentials/port, and trims a
20
+ * trailing `.git` / slashes. Returns null when unparseable.
21
+ */
22
+ export declare function normalizeRemoteUrl(url: string): string | null;
23
+ /**
24
+ * Do two identities point at the same repository?
25
+ * Common-dir (main root) equality wins; otherwise fall back to remote
26
+ * equality, but only when both remotes are defined.
27
+ */
28
+ export declare function sameRepo(a: RepoIdentity | null, b: RepoIdentity | null): boolean;
29
+ //# sourceMappingURL=repo-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-identity.d.ts","sourceRoot":"","sources":["../../src/lib/repo-identity.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,uFAAuF;IACvF,OAAO,EAAE,MAAM,CAAA;IACf,yEAAyE;IACzE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAID,oDAAoD;AACpD,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAOpE;AAyCD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA8B7D;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,YAAY,GAAG,IAAI,EAAE,CAAC,EAAE,YAAY,GAAG,IAAI,GAAG,OAAO,CAKhF"}
@@ -0,0 +1,113 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, realpathSync } from 'node:fs';
3
+ import { basename, dirname, resolve } from 'node:path';
4
+ const identityCache = new Map();
5
+ /** Reset the per-process identity cache (tests). */
6
+ export function clearRepoIdentityCache() {
7
+ identityCache.clear();
8
+ }
9
+ /**
10
+ * Resolve the repo identity for a directory: main-repo root via
11
+ * `git rev-parse --git-common-dir` plus the normalized origin remote.
12
+ * Nonexistent dirs and git failures resolve to null (unresolved).
13
+ * Results are cached per dir for the life of the process.
14
+ */
15
+ export function resolveRepoIdentity(dir) {
16
+ const key = resolve(dir);
17
+ const cached = identityCache.get(key);
18
+ if (cached !== undefined)
19
+ return cached;
20
+ const identity = computeIdentity(key);
21
+ identityCache.set(key, identity);
22
+ return identity;
23
+ }
24
+ function computeIdentity(dir) {
25
+ if (!existsSync(dir))
26
+ return null;
27
+ const commonDirOut = git(dir, ['rev-parse', '--git-common-dir']);
28
+ if (!commonDirOut)
29
+ return null;
30
+ const commonDir = resolve(dir, commonDirOut);
31
+ // Canonicalize so a session recorded under a symlinked path (macOS
32
+ // /var → /private/var, ~/repos symlinks) still matches the binding root,
33
+ // which git reports fully resolved.
34
+ const rootDir = canonical(basename(commonDir) === '.git' ? dirname(commonDir) : commonDir);
35
+ const remoteOut = git(dir, ['config', '--get', 'remote.origin.url']);
36
+ const remote = remoteOut ? normalizeRemoteUrl(remoteOut) : null;
37
+ return { rootDir, remote };
38
+ }
39
+ function canonical(path) {
40
+ try {
41
+ return realpathSync(path);
42
+ }
43
+ catch {
44
+ return path;
45
+ }
46
+ }
47
+ function git(dir, args) {
48
+ try {
49
+ const res = spawnSync('git', ['-C', dir, ...args], {
50
+ encoding: 'utf-8',
51
+ stdio: ['ignore', 'pipe', 'ignore'],
52
+ });
53
+ if (res.error || res.status !== 0)
54
+ return null;
55
+ const out = res.stdout.trim();
56
+ return out.length > 0 ? out : null;
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
62
+ /**
63
+ * Normalize a git remote URL so transport variants compare equal:
64
+ * `git@github.com:ajust-au/agent.git` === `https://github.com/ajust-au/agent`.
65
+ * Lowercases the host, strips protocol/credentials/port, and trims a
66
+ * trailing `.git` / slashes. Returns null when unparseable.
67
+ */
68
+ export function normalizeRemoteUrl(url) {
69
+ const trimmed = url.trim();
70
+ if (!trimmed)
71
+ return null;
72
+ let host;
73
+ let path;
74
+ // Protocol form: https://host/path, ssh://git@host:port/path, git://host/path
75
+ const protoMatch = /^[a-z][a-z0-9+.-]*:\/\/(?:[^@/]+@)?([^/:]+)(?::\d+)?\/(.*)$/i.exec(trimmed);
76
+ // SCP-like form: git@host:path
77
+ const scpMatch = /^(?:[^@/]+@)?([^/:]+):(.*)$/.exec(trimmed);
78
+ if (protoMatch) {
79
+ host = protoMatch[1];
80
+ path = protoMatch[2];
81
+ }
82
+ else if (scpMatch) {
83
+ host = scpMatch[1];
84
+ path = scpMatch[2];
85
+ }
86
+ else {
87
+ return null;
88
+ }
89
+ if (!host || path === undefined)
90
+ return null;
91
+ const cleanPath = path
92
+ .replace(/\.git$/i, '')
93
+ .replace(/\/+$/, '')
94
+ .replace(/^\/+/, '');
95
+ if (!cleanPath)
96
+ return null;
97
+ return `${host.toLowerCase()}/${cleanPath}`;
98
+ }
99
+ /**
100
+ * Do two identities point at the same repository?
101
+ * Common-dir (main root) equality wins; otherwise fall back to remote
102
+ * equality, but only when both remotes are defined.
103
+ */
104
+ export function sameRepo(a, b) {
105
+ if (!a || !b)
106
+ return false;
107
+ if (a.rootDir === b.rootDir)
108
+ return true;
109
+ if (a.remote !== null && b.remote !== null)
110
+ return a.remote === b.remote;
111
+ return false;
112
+ }
113
+ //# sourceMappingURL=repo-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo-identity.js","sourceRoot":"","sources":["../../src/lib/repo-identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * `halyard setup` scaffolding for session upload: a tiny launcher plus one
3
+ * lifecycle-hook config per client, all pointing at `halyard hook`.
4
+ *
5
+ * Merges are idempotent and keyed on the launcher path, so re-running setup
6
+ * (or after a template change) updates our entries and leaves everything else alone.
7
+ */
8
+ export declare const HOOK_LAUNCHER_PATH = ".halyard/hook.sh";
9
+ export type HookScaffoldTarget = 'launcher' | 'codex' | 'cursor' | 'gemini' | 'copilot' | 'opencode' | 'grok';
10
+ export type HookScaffoldStatus = 'updated' | 'unchanged' | 'error';
11
+ export interface HookScaffoldResult {
12
+ target: HookScaffoldTarget;
13
+ status: HookScaffoldStatus;
14
+ location: string;
15
+ detail: string;
16
+ }
17
+ export interface HookScaffoldOptions {
18
+ dryRun?: boolean;
19
+ }
20
+ export interface LifecycleHookSpec {
21
+ start: Record<string, unknown>;
22
+ end: Record<string, unknown>;
23
+ }
24
+ export declare function launcherScript(): string;
25
+ export declare function claudeHookSpec(): LifecycleHookSpec;
26
+ /** Codex caps SessionEnd at 3 seconds, so the end hook detaches. */
27
+ export declare function codexHookSpec(): LifecycleHookSpec;
28
+ /** Gemini timeouts are milliseconds and entries carry a name. */
29
+ export declare function geminiHookSpec(): LifecycleHookSpec;
30
+ /** Merge start/end hooks into a Claude-shaped settings object (Claude, Codex, Gemini). */
31
+ export declare function mergeLifecycleHooks(input: unknown, spec: LifecycleHookSpec, events?: {
32
+ start: string;
33
+ end: string;
34
+ }): Record<string, unknown>;
35
+ export declare function mergeCursorHooks(input: unknown): Record<string, unknown>;
36
+ export declare function copilotHooksFile(): string;
37
+ export declare function grokHooksFile(): string;
38
+ export declare function opencodePluginFile(): string;
39
+ /** Everything except Claude's settings.json, which `runSetup` owns (it also enables the plugin there). */
40
+ export declare function scaffoldHooks(cwd: string, options?: HookScaffoldOptions): HookScaffoldResult[];
41
+ //# sourceMappingURL=setup-hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-hooks.d.ts","sourceRoot":"","sources":["../../src/lib/setup-hooks.ts"],"names":[],"mappings":"AAKA;;;;;;GAMG;AAEH,eAAO,MAAM,kBAAkB,qBAAqB,CAAA;AAEpD,MAAM,MAAM,kBAAkB,GAC1B,UAAU,GACV,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,SAAS,GACT,UAAU,GACV,MAAM,CAAA;AACV,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,CAAA;AAElE,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,kBAAkB,CAAA;IAC1B,MAAM,EAAE,kBAAkB,CAAA;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC7B;AAED,wBAAgB,cAAc,IAAI,MAAM,CAgBvC;AAMD,wBAAgB,cAAc,IAAI,iBAAiB,CAiBlD;AAED,oEAAoE;AACpE,wBAAgB,aAAa,IAAI,iBAAiB,CAejD;AAED,iEAAiE;AACjE,wBAAgB,cAAc,IAAI,iBAAiB,CAiBlD;AAgCD,0FAA0F;AAC1F,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,iBAAiB,EACvB,MAAM,GAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAiD,GACpF,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWxE;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAOzC;AAED,wBAAgB,aAAa,IAAI,MAAM,CAWtC;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAkE3C;AA8ED,0GAA0G;AAC1G,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,EAAE,CAoCtB"}
@@ -0,0 +1,276 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { isDeepStrictEqual } from 'node:util';
4
+ import { asObject, formatError, readJsonFile, writeJsonFile } from './config-files.js';
5
+ /**
6
+ * `halyard setup` scaffolding for session upload: a tiny launcher plus one
7
+ * lifecycle-hook config per client, all pointing at `halyard hook`.
8
+ *
9
+ * Merges are idempotent and keyed on the launcher path, so re-running setup
10
+ * (or after a template change) updates our entries and leaves everything else alone.
11
+ */
12
+ export const HOOK_LAUNCHER_PATH = '.halyard/hook.sh';
13
+ export function launcherScript() {
14
+ return `#!/bin/sh
15
+ # Generated by \`halyard setup\`. Forwards agent lifecycle hooks to \`halyard hook\`
16
+ # when the Halyard CLI is available, and exits 0 otherwise so the agent is
17
+ # never blocked. Re-run \`halyard setup\` to regenerate.
18
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
19
+ if [ -f "$ROOT/apps/cli/dist/cli.js" ] && grep -q '"name": "@halyard/cli"' "$ROOT/apps/cli/package.json" 2>/dev/null; then
20
+ exec node "$ROOT/apps/cli/dist/cli.js" hook --root "$ROOT" "$@"
21
+ fi
22
+ BIN="$ROOT/node_modules/.bin/halyard"
23
+ [ -x "$BIN" ] || BIN="$(command -v halyard 2>/dev/null || true)"
24
+ [ -n "$BIN" ] || exit 0
25
+ # Older CLI releases have no \`hook\` command (and answer \`--help\` for any word).
26
+ "$BIN" --help 2>/dev/null | grep -q '^ hook' || exit 0
27
+ exec "$BIN" hook --root "$ROOT" "$@"
28
+ `;
29
+ }
30
+ function hookArgs(client, event) {
31
+ return `--client ${client} --event ${event}`;
32
+ }
33
+ export function claudeHookSpec() {
34
+ const command = (event) => `"$CLAUDE_PROJECT_DIR"/${HOOK_LAUNCHER_PATH} ${hookArgs('claude', event)}`;
35
+ return {
36
+ start: {
37
+ type: 'command',
38
+ command: command('start'),
39
+ timeout: 10,
40
+ statusMessage: 'Sweeping unsynced agent sessions...',
41
+ },
42
+ end: {
43
+ type: 'command',
44
+ command: command('end'),
45
+ timeout: 60,
46
+ statusMessage: 'Uploading agent session to Halyard...',
47
+ },
48
+ };
49
+ }
50
+ /** Codex caps SessionEnd at 3 seconds, so the end hook detaches. */
51
+ export function codexHookSpec() {
52
+ const prefix = 'cd "${CODEX_CWD:-$PWD}" && ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" && "$ROOT"/';
53
+ return {
54
+ start: {
55
+ type: 'command',
56
+ command: `${prefix}${HOOK_LAUNCHER_PATH} ${hookArgs('codex', 'start')}`,
57
+ timeout: 10,
58
+ },
59
+ end: {
60
+ type: 'command',
61
+ command: `${prefix}${HOOK_LAUNCHER_PATH} ${hookArgs('codex', 'end')} --detach`,
62
+ timeout: 3,
63
+ },
64
+ };
65
+ }
66
+ /** Gemini timeouts are milliseconds and entries carry a name. */
67
+ export function geminiHookSpec() {
68
+ const command = (event) => `"$GEMINI_PROJECT_DIR"/${HOOK_LAUNCHER_PATH} ${hookArgs('gemini', event)}`;
69
+ return {
70
+ start: {
71
+ type: 'command',
72
+ name: 'halyard-session-sweep',
73
+ command: command('start'),
74
+ timeout: 10_000,
75
+ },
76
+ end: {
77
+ type: 'command',
78
+ name: 'halyard-session-upload',
79
+ command: command('end'),
80
+ timeout: 60_000,
81
+ },
82
+ };
83
+ }
84
+ function isLauncherEntry(entry) {
85
+ const command = entry['command'] ?? entry['bash'];
86
+ return typeof command === 'string' && command.includes(HOOK_LAUNCHER_PATH);
87
+ }
88
+ /** Claude-shaped: event → [{ matcher?, hooks: [entry, ...] }]. */
89
+ function upsertGrouped(groupsValue, hook) {
90
+ const groups = Array.isArray(groupsValue) ? groupsValue.map(asObject) : [];
91
+ let placed = false;
92
+ const next = groups.map((group) => {
93
+ const entries = Array.isArray(group['hooks']) ? group['hooks'].map(asObject) : [];
94
+ const index = entries.findIndex(isLauncherEntry);
95
+ if (index === -1)
96
+ return group;
97
+ placed = true;
98
+ entries[index] = hook;
99
+ return { ...group, hooks: entries };
100
+ });
101
+ if (!placed)
102
+ next.push({ hooks: [hook] });
103
+ return next;
104
+ }
105
+ /** Cursor-shaped: event → [entry, ...]. */
106
+ function upsertFlat(listValue, entry) {
107
+ const list = Array.isArray(listValue) ? listValue.map(asObject) : [];
108
+ const index = list.findIndex(isLauncherEntry);
109
+ if (index === -1)
110
+ list.push(entry);
111
+ else
112
+ list[index] = entry;
113
+ return list;
114
+ }
115
+ /** Merge start/end hooks into a Claude-shaped settings object (Claude, Codex, Gemini). */
116
+ export function mergeLifecycleHooks(input, spec, events = { start: 'SessionStart', end: 'SessionEnd' }) {
117
+ const settings = asObject(input);
118
+ const hooks = asObject(settings['hooks']);
119
+ hooks[events.start] = upsertGrouped(hooks[events.start], spec.start);
120
+ hooks[events.end] = upsertGrouped(hooks[events.end], spec.end);
121
+ return { ...settings, hooks };
122
+ }
123
+ export function mergeCursorHooks(input) {
124
+ const config = asObject(input);
125
+ const hooks = asObject(config['hooks']);
126
+ const entry = (event) => ({
127
+ command: `./${HOOK_LAUNCHER_PATH} ${hookArgs('cursor', event)}`,
128
+ timeout: event === 'end' ? 60 : 10,
129
+ });
130
+ hooks['sessionStart'] = upsertFlat(hooks['sessionStart'], entry('start'));
131
+ hooks['sessionEnd'] = upsertFlat(hooks['sessionEnd'], entry('end'));
132
+ const version = typeof config['version'] === 'number' ? config['version'] : 1;
133
+ return { ...config, version, hooks };
134
+ }
135
+ export function copilotHooksFile() {
136
+ const entry = (event) => ({
137
+ type: 'command',
138
+ bash: `./${HOOK_LAUNCHER_PATH} ${hookArgs('copilot', event)}`,
139
+ timeoutSec: event === 'end' ? 60 : 10,
140
+ });
141
+ return `${JSON.stringify({ version: 1, hooks: { sessionStart: [entry('start')], sessionEnd: [entry('end')] } }, null, 2)}\n`;
142
+ }
143
+ export function grokHooksFile() {
144
+ const entry = (event) => ({
145
+ hooks: [
146
+ {
147
+ type: 'command',
148
+ command: `"\${GROK_WORKSPACE_ROOT:-.}"/${HOOK_LAUNCHER_PATH} ${hookArgs('grok', event)}`,
149
+ timeout: event === 'end' ? 60 : 10,
150
+ },
151
+ ],
152
+ });
153
+ return `${JSON.stringify({ hooks: { SessionStart: [entry('start')], SessionEnd: [entry('end')] } }, null, 2)}\n`;
154
+ }
155
+ export function opencodePluginFile() {
156
+ return `// Generated by \`halyard setup\`. OpenCode has no shell-command hooks — extensibility
157
+ // is TypeScript plugins on its event bus — so this bridges its session lifecycle
158
+ // to the same uploader every other client uses: ${HOOK_LAUNCHER_PATH} -> \`halyard hook\`.
159
+ //
160
+ // * startup -> sweeps sessions that never uploaded
161
+ // * session.idle -> end-of-session upload, debounced: OpenCode has no
162
+ // session-end event, and the ingest endpoint keeps the FIRST
163
+ // upload it sees for a session id, so we wait for the
164
+ // session to go quiet before sending it.
165
+
166
+ const QUIET_MS = 10 * 60 * 1000
167
+
168
+ interface OpencodeEvent {
169
+ type: string
170
+ properties?: { sessionID?: string; info?: { id?: string } }
171
+ }
172
+
173
+ interface PluginContext {
174
+ directory: string
175
+ $: (
176
+ strings: TemplateStringsArray,
177
+ ...values: unknown[]
178
+ ) => {
179
+ quiet(): { nothrow(): Promise<unknown> }
180
+ }
181
+ }
182
+
183
+ export const HalyardSessionUpload = async ({ $, directory }: PluginContext) => {
184
+ const launcher = \`\${directory}/${HOOK_LAUNCHER_PATH}\`
185
+ const timers = new Map<string, ReturnType<typeof setTimeout>>()
186
+
187
+ const run = async (event: 'start' | 'end') => {
188
+ const result = $\`\${launcher} --client opencode --event \${event} --detach\`
189
+ await result.quiet().nothrow()
190
+ }
191
+
192
+ await run('start')
193
+
194
+ return {
195
+ event: async ({ event }: { event: OpencodeEvent }) => {
196
+ const sessionId = event.properties?.sessionID ?? event.properties?.info?.id ?? 'default'
197
+ if (event.type === 'session.idle') {
198
+ const pending = timers.get(sessionId)
199
+ if (pending) clearTimeout(pending)
200
+ timers.set(
201
+ sessionId,
202
+ setTimeout(() => {
203
+ timers.delete(sessionId)
204
+ void run('end')
205
+ }, QUIET_MS),
206
+ )
207
+ return
208
+ }
209
+ // Any further activity on the session restarts the quiet period.
210
+ if (event.type === 'message.updated' || event.type === 'session.updated') {
211
+ const pending = timers.get(sessionId)
212
+ if (pending) {
213
+ clearTimeout(pending)
214
+ timers.delete(sessionId)
215
+ }
216
+ }
217
+ },
218
+ }
219
+ }
220
+ `;
221
+ }
222
+ function scaffoldResult(target, status, location, detail) {
223
+ return { target, status, location, detail };
224
+ }
225
+ /** Write a file we own outright (created or replaced when its content differs). */
226
+ function writeOwnedFile(target, filePath, content, dryRun, executable = false) {
227
+ const current = existsSync(filePath) ? readFileSync(filePath, 'utf8') : null;
228
+ if (current === content) {
229
+ return scaffoldResult(target, 'unchanged', filePath, 'Halyard session-upload hook is already in place.');
230
+ }
231
+ if (!dryRun) {
232
+ mkdirSync(dirname(filePath), { recursive: true });
233
+ writeFileSync(filePath, content);
234
+ if (executable)
235
+ chmodSync(filePath, 0o755);
236
+ }
237
+ const verb = current === null ? 'create' : 'update';
238
+ return scaffoldResult(target, 'updated', filePath, dryRun
239
+ ? `Would ${verb} the Halyard session-upload hook.`
240
+ : `${verb === 'create' ? 'Created' : 'Updated'} the Halyard session-upload hook.`);
241
+ }
242
+ /** Merge our entries into a JSON config other tools also write to. */
243
+ function mergeJsonFile(target, filePath, merge, dryRun) {
244
+ const current = readJsonFile(filePath);
245
+ if (current instanceof Error)
246
+ return scaffoldResult(target, 'error', filePath, current.message);
247
+ let next;
248
+ try {
249
+ next = merge(current);
250
+ }
251
+ catch (error) {
252
+ return scaffoldResult(target, 'error', filePath, formatError(error));
253
+ }
254
+ if (isDeepStrictEqual(current, next)) {
255
+ return scaffoldResult(target, 'unchanged', filePath, 'Halyard session-upload hooks are already present.');
256
+ }
257
+ if (!dryRun)
258
+ writeJsonFile(filePath, next);
259
+ return scaffoldResult(target, 'updated', filePath, dryRun
260
+ ? 'Would add the Halyard session-upload hooks.'
261
+ : 'Added the Halyard session-upload hooks.');
262
+ }
263
+ /** Everything except Claude's settings.json, which `runSetup` owns (it also enables the plugin there). */
264
+ export function scaffoldHooks(cwd, options = {}) {
265
+ const dryRun = options.dryRun ?? false;
266
+ return [
267
+ writeOwnedFile('launcher', join(cwd, HOOK_LAUNCHER_PATH), launcherScript(), dryRun, true),
268
+ mergeJsonFile('codex', join(cwd, '.codex', 'hooks.json'), (c) => mergeLifecycleHooks(c, codexHookSpec()), dryRun),
269
+ mergeJsonFile('cursor', join(cwd, '.cursor', 'hooks.json'), (c) => mergeCursorHooks(c), dryRun),
270
+ mergeJsonFile('gemini', join(cwd, '.gemini', 'settings.json'), (c) => mergeLifecycleHooks(c, geminiHookSpec()), dryRun),
271
+ writeOwnedFile('copilot', join(cwd, '.github', 'hooks', 'halyard-session-upload.json'), copilotHooksFile(), dryRun),
272
+ writeOwnedFile('opencode', join(cwd, '.opencode', 'plugins', 'halyard-session-upload.ts'), opencodePluginFile(), dryRun),
273
+ writeOwnedFile('grok', join(cwd, '.grok', 'hooks', 'halyard-session-upload.json'), grokHooksFile(), dryRun),
274
+ ];
275
+ }
276
+ //# sourceMappingURL=setup-hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-hooks.js","sourceRoot":"","sources":["../../src/lib/setup-hooks.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,9 @@
1
+ import { type HookScaffoldTarget } from './setup-hooks.js';
1
2
  export declare const HALYARD_MCP_URL = "https://mcp.usehalyard.ai";
2
3
  export declare const HALYARD_CLAUDE_MARKETPLACE_NAME = "halyard-labs";
3
4
  export declare const HALYARD_CLAUDE_MARKETPLACE_REPO = "halyard-labs/claude-plugin-marketplace";
4
5
  export declare const HALYARD_CLAUDE_PLUGIN_ID = "halyard@halyard-labs";
5
- export type SetupTarget = 'claude' | 'codex' | 'cursor';
6
+ export type SetupTarget = 'claude' | 'codex' | 'cursor' | HookScaffoldTarget;
6
7
  export type SetupStatus = 'updated' | 'unchanged' | 'skipped' | 'error';
7
8
  export interface SetupResult {
8
9
  target: SetupTarget;
@@ -19,6 +20,8 @@ export interface SetupOptions {
19
20
  dryRun?: boolean;
20
21
  homeDir?: string;
21
22
  runner?: CommandRunner;
23
+ /** Write the session-upload hooks (launcher + per-client config). Default true. */
24
+ hooks?: boolean;
22
25
  }
23
26
  interface ClaudeSettings extends Record<string, unknown> {
24
27
  enabledPlugins?: Record<string, unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/lib/setup.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,eAAe,8BAA8B,CAAA;AAC1D,eAAO,MAAM,+BAA+B,iBAAiB,CAAA;AAC7D,eAAO,MAAM,+BAA+B,2CAA2C,CAAA;AACvF,eAAO,MAAM,wBAAwB,yBAAyB,CAAA;AAE9D,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAA;AACvD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAA;AAEvE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,SAAS,GAAG,MAAM,CAAA;IACzB,MAAM,EAAE,WAAW,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAA;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,aAAa,CAAA;CACvB;AAED,UAAU,cAAe,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACtD,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACxC,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACjD;AAED,UAAU,YAAa,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACrC;AAED,UAAU,WAAY,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACtC;AAED,wBAAgB,kBAAkB,IAAI,aAAa,CAMlD;AAED,wBAAgB,QAAQ,CAAC,OAAO,GAAE,YAAiB,GAAG,WAAW,EAAE,CAYlE;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;EAMzD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAkBlE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAc9D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAe5D"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/lib/setup.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,kBAAkB,CAAA;AAEzB,eAAO,MAAM,eAAe,8BAA8B,CAAA;AAC1D,eAAO,MAAM,+BAA+B,iBAAiB,CAAA;AAC7D,eAAO,MAAM,+BAA+B,2CAA2C,CAAA;AACvF,eAAO,MAAM,wBAAwB,yBAAyB,CAAA;AAE9D,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,kBAAkB,CAAA;AAC5E,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAA;AAEvE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,SAAS,GAAG,MAAM,CAAA;IACzB,MAAM,EAAE,WAAW,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAA;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,aAAa,CAAA;IACtB,mFAAmF;IACnF,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,UAAU,cAAe,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACtD,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACxC,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACjD;AAED,UAAU,YAAa,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACpD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACrC;AAED,UAAU,WAAY,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACtC;AAED,wBAAgB,kBAAkB,IAAI,aAAa,CAMlD;AAED,wBAAgB,QAAQ,CAAC,OAAO,GAAE,YAAiB,GAAG,WAAW,EAAE,CAmBlE;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;EAMzD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAkBlE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAc9D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAe5D"}
package/dist/lib/setup.js CHANGED
@@ -4,6 +4,8 @@ import { homedir } from 'node:os';
4
4
  import { dirname, join, relative, resolve } from 'node:path';
5
5
  import { isDeepStrictEqual } from 'node:util';
6
6
  import { parse as parseToml, stringify as stringifyToml } from 'smol-toml';
7
+ import { asObject, formatError, readJsonFile, writeJsonFile } from './config-files.js';
8
+ import { claudeHookSpec, mergeLifecycleHooks, scaffoldHooks, } from './setup-hooks.js';
7
9
  export const HALYARD_MCP_URL = 'https://mcp.usehalyard.ai';
8
10
  export const HALYARD_CLAUDE_MARKETPLACE_NAME = 'halyard-labs';
9
11
  export const HALYARD_CLAUDE_MARKETPLACE_REPO = 'halyard-labs/claude-plugin-marketplace';
@@ -20,12 +22,19 @@ export function runSetup(options = {}) {
20
22
  const dryRun = options.dryRun ?? false;
21
23
  const homeDir = options.homeDir ?? homedir();
22
24
  const runner = options.runner ?? createSystemRunner();
25
+ const hooks = options.hooks ?? true;
23
26
  const paths = getSetupPaths(cwd, homeDir);
24
- return [
25
- setupClaudeProject(paths.claudeProject, dryRun),
27
+ const results = [
28
+ setupClaudeProject(paths.claudeProject, dryRun, hooks),
26
29
  setupCursorProject(paths.cursorProject, dryRun),
27
30
  setupCodexUser(paths.codexUser, dryRun, runner),
28
31
  ];
32
+ if (hooks) {
33
+ for (const entry of scaffoldHooks(cwd, { dryRun })) {
34
+ results.push({ ...entry, scope: 'project' });
35
+ }
36
+ }
37
+ return results;
29
38
  }
30
39
  export function getSetupPaths(cwd, homeDir) {
31
40
  return {
@@ -78,21 +87,24 @@ export function mergeCodexConfig(input) {
78
87
  mcp_servers: mcpServers,
79
88
  };
80
89
  }
81
- function setupClaudeProject(filePath, dryRun) {
90
+ function setupClaudeProject(filePath, dryRun, hooks) {
82
91
  const current = readJsonFile(filePath);
83
92
  if (current instanceof Error) {
84
93
  return result('claude', 'project', 'error', filePath, current.message);
85
94
  }
86
- const next = mergeClaudeSettings(current);
95
+ let next = mergeClaudeSettings(current);
96
+ if (hooks)
97
+ next = mergeLifecycleHooks(next, claudeHookSpec());
98
+ const what = hooks
99
+ ? 'the Halyard marketplace, the project plugin and the session-upload hooks'
100
+ : 'the Halyard marketplace and the project plugin';
87
101
  if (isDeepStrictEqual(current, next)) {
88
- return result('claude', 'project', 'unchanged', filePath, 'Halyard plugin and marketplace are already scaffolded.');
102
+ return result('claude', 'project', 'unchanged', filePath, `Already scaffolded: ${what}.`);
89
103
  }
90
104
  if (!dryRun) {
91
105
  writeJsonFile(filePath, next);
92
106
  }
93
- return result('claude', 'project', 'updated', filePath, dryRun
94
- ? 'Would add the Halyard marketplace and enable the project plugin.'
95
- : 'Added the Halyard marketplace and enabled the project plugin.');
107
+ return result('claude', 'project', 'updated', filePath, dryRun ? `Would add ${what}.` : `Added ${what}.`);
96
108
  }
97
109
  function setupCursorProject(filePath, dryRun) {
98
110
  const current = readJsonFile(filePath);
@@ -130,20 +142,6 @@ function setupCodexUser(filePath, dryRun, runner) {
130
142
  ? 'Would add the Halyard MCP server to Codex user config.'
131
143
  : 'Added the Halyard MCP server to Codex user config.');
132
144
  }
133
- function readJsonFile(filePath) {
134
- if (!existsSync(filePath))
135
- return {};
136
- try {
137
- return JSON.parse(readFileSync(filePath, 'utf8'));
138
- }
139
- catch (error) {
140
- return new Error(`Could not parse JSON at ${filePath}: ${formatError(error)}`);
141
- }
142
- }
143
- function writeJsonFile(filePath, value) {
144
- mkdirSync(dirname(filePath), { recursive: true });
145
- writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
146
- }
147
145
  function readTomlFile(filePath) {
148
146
  if (!existsSync(filePath))
149
147
  return {};
@@ -158,15 +156,6 @@ function writeTomlFile(filePath, value) {
158
156
  mkdirSync(dirname(filePath), { recursive: true });
159
157
  writeFileSync(filePath, `${stringifyToml(asObject(value))}\n`);
160
158
  }
161
- function asObject(value) {
162
- if (value && typeof value === 'object' && !Array.isArray(value)) {
163
- return { ...value };
164
- }
165
- return {};
166
- }
167
- function formatError(error) {
168
- return error instanceof Error ? error.message : String(error);
169
- }
170
159
  function result(target, scope, status, filePath, detail) {
171
160
  return {
172
161
  target,