harnery 0.6.0 → 0.7.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 (137) hide show
  1. package/README.md +16 -6
  2. package/dist/commander.d.ts +19 -0
  3. package/dist/commander.d.ts.map +1 -1
  4. package/dist/commander.js +2 -0
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +51 -4
  7. package/dist/commands/deinit.d.ts.map +1 -1
  8. package/dist/commands/deinit.js +4 -0
  9. package/dist/commands/devtools.d.ts +4 -0
  10. package/dist/commands/devtools.d.ts.map +1 -0
  11. package/dist/commands/devtools.js +239 -0
  12. package/dist/commands/docs.d.ts.map +1 -1
  13. package/dist/commands/docs.js +69 -1
  14. package/dist/commands/doctor.js +12 -4
  15. package/dist/commands/env.d.ts.map +1 -1
  16. package/dist/commands/env.js +3 -63
  17. package/dist/commands/init.d.ts +1 -0
  18. package/dist/commands/init.d.ts.map +1 -1
  19. package/dist/commands/init.js +54 -14
  20. package/dist/commands/scratch.js +1 -1
  21. package/dist/commands/tunnel.d.ts.map +1 -1
  22. package/dist/commands/tunnel.js +273 -62
  23. package/dist/commands/web-fetch.js +1 -1
  24. package/dist/core/agents/cli.js +48 -0
  25. package/dist/core/agents/coord-client.d.ts.map +1 -1
  26. package/dist/core/agents/coord-client.js +32 -8
  27. package/dist/core/agents/events/emit.d.ts.map +1 -1
  28. package/dist/core/agents/events/emit.js +4 -0
  29. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
  30. package/dist/core/agents/rules/claim-conflict.js +16 -5
  31. package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -1
  32. package/dist/core/agents/state/heartbeat-projector.js +10 -3
  33. package/dist/core/config.d.ts +10 -0
  34. package/dist/core/config.d.ts.map +1 -1
  35. package/dist/core/config.js +13 -0
  36. package/dist/core/hooks/cli.js +3 -3
  37. package/dist/core/hooks/effects/index.d.ts +11 -7
  38. package/dist/core/hooks/effects/index.d.ts.map +1 -1
  39. package/dist/core/hooks/effects/index.js +15 -18
  40. package/dist/core/hooks/events/emit.d.ts.map +1 -1
  41. package/dist/core/hooks/events/emit.js +4 -0
  42. package/dist/core/hooks/events/rotate.d.ts +43 -0
  43. package/dist/core/hooks/events/rotate.d.ts.map +1 -0
  44. package/dist/core/hooks/events/rotate.js +142 -0
  45. package/dist/core/hooks/harness/events.d.ts +11 -1
  46. package/dist/core/hooks/harness/events.d.ts.map +1 -1
  47. package/dist/core/hooks/harness/events.js +22 -3
  48. package/dist/core/hooks/harness/wiring.d.ts +8 -0
  49. package/dist/core/hooks/harness/wiring.d.ts.map +1 -1
  50. package/dist/core/hooks/harness/wiring.js +34 -5
  51. package/dist/core/scratch/index.d.ts.map +1 -0
  52. package/dist/{lib → core}/scratch/index.js +2 -2
  53. package/dist/lib/devtools.d.ts +178 -0
  54. package/dist/lib/devtools.d.ts.map +1 -0
  55. package/dist/lib/devtools.js +1328 -0
  56. package/dist/lib/docs-frontmatter-migrate.d.ts +33 -0
  57. package/dist/lib/docs-frontmatter-migrate.d.ts.map +1 -0
  58. package/dist/lib/docs-frontmatter-migrate.js +364 -0
  59. package/dist/lib/docs-frontmatter.d.ts +33 -0
  60. package/dist/lib/docs-frontmatter.d.ts.map +1 -0
  61. package/dist/lib/docs-frontmatter.js +130 -0
  62. package/dist/lib/docs-index.d.ts +1 -0
  63. package/dist/lib/docs-index.d.ts.map +1 -1
  64. package/dist/lib/docs-index.js +4 -5
  65. package/dist/lib/docs-lint.d.ts +2 -0
  66. package/dist/lib/docs-lint.d.ts.map +1 -1
  67. package/dist/lib/docs-lint.js +18 -12
  68. package/dist/lib/docs-meta.d.ts +14 -0
  69. package/dist/lib/docs-meta.d.ts.map +1 -0
  70. package/dist/lib/docs-meta.js +34 -0
  71. package/dist/lib/docs-sweep.d.ts +12 -0
  72. package/dist/lib/docs-sweep.d.ts.map +1 -1
  73. package/dist/lib/docs-sweep.js +98 -103
  74. package/dist/lib/format.js +2 -2
  75. package/dist/lib/http/index.d.ts +1 -0
  76. package/dist/lib/http/index.d.ts.map +1 -1
  77. package/dist/lib/http/index.js +1 -0
  78. package/dist/lib/http/request.d.ts +77 -0
  79. package/dist/lib/http/request.d.ts.map +1 -0
  80. package/dist/lib/http/request.js +105 -0
  81. package/dist/lib/instructions/apply.d.ts +63 -0
  82. package/dist/lib/instructions/apply.d.ts.map +1 -0
  83. package/dist/lib/instructions/apply.js +255 -0
  84. package/dist/lib/instructions/splice.d.ts +73 -0
  85. package/dist/lib/instructions/splice.d.ts.map +1 -0
  86. package/dist/lib/instructions/splice.js +118 -0
  87. package/dist/lib/instructions/templates.d.ts +45 -0
  88. package/dist/lib/instructions/templates.d.ts.map +1 -0
  89. package/dist/lib/instructions/templates.js +258 -0
  90. package/dist/lib/tunnel/gate.d.ts +1 -0
  91. package/dist/lib/tunnel/gate.d.ts.map +1 -1
  92. package/dist/lib/tunnel/gate.js +14 -9
  93. package/dist/lib/tunnel/state.d.ts +11 -1
  94. package/dist/lib/tunnel/state.d.ts.map +1 -1
  95. package/dist/lib/tunnel/state.js +8 -3
  96. package/package.json +7 -6
  97. package/src/commander.ts +23 -0
  98. package/src/commands/agents.ts +50 -3
  99. package/src/commands/deinit.ts +5 -0
  100. package/src/commands/devtools.ts +284 -0
  101. package/src/commands/docs.ts +81 -1
  102. package/src/commands/doctor.ts +13 -4
  103. package/src/commands/env.ts +11 -77
  104. package/src/commands/init.ts +66 -15
  105. package/src/commands/scratch.ts +1 -1
  106. package/src/commands/tunnel.ts +316 -65
  107. package/src/commands/web-fetch.ts +1 -1
  108. package/src/core/agents/cli.ts +55 -0
  109. package/src/core/agents/coord-client.ts +34 -7
  110. package/src/core/agents/events/emit.ts +5 -0
  111. package/src/core/agents/rules/claim-conflict.ts +17 -6
  112. package/src/core/agents/state/heartbeat-projector.ts +11 -3
  113. package/src/core/config.ts +14 -0
  114. package/src/core/hooks/cli.ts +3 -3
  115. package/src/core/hooks/effects/index.ts +23 -17
  116. package/src/core/hooks/events/emit.ts +5 -0
  117. package/src/core/hooks/events/rotate.ts +151 -0
  118. package/src/core/hooks/harness/events.ts +30 -3
  119. package/src/core/hooks/harness/wiring.ts +46 -5
  120. package/src/{lib → core}/scratch/index.ts +2 -2
  121. package/src/lib/devtools.ts +1653 -0
  122. package/src/lib/docs-frontmatter-migrate.ts +427 -0
  123. package/src/lib/docs-frontmatter.ts +151 -0
  124. package/src/lib/docs-index.ts +4 -5
  125. package/src/lib/docs-lint.ts +17 -11
  126. package/src/lib/docs-meta.ts +44 -0
  127. package/src/lib/docs-sweep.ts +104 -102
  128. package/src/lib/format.ts +2 -2
  129. package/src/lib/http/index.ts +1 -0
  130. package/src/lib/http/request.ts +154 -0
  131. package/src/lib/instructions/apply.ts +318 -0
  132. package/src/lib/instructions/splice.ts +148 -0
  133. package/src/lib/instructions/templates.ts +295 -0
  134. package/src/lib/tunnel/gate.ts +14 -9
  135. package/src/lib/tunnel/state.ts +19 -4
  136. package/dist/lib/scratch/index.d.ts.map +0 -1
  137. /package/dist/{lib → core}/scratch/index.d.ts +0 -0
@@ -37,19 +37,35 @@ export const CURSOR_EVENTS = [
37
37
  { settingsKey: "beforeSubmitPrompt", subcommand: "user-prompt-submit" },
38
38
  { settingsKey: "stop", subcommand: "stop" },
39
39
  ];
40
- /** Codex: `.codex/hooks.json`. PascalCase keys + the same entry shape as Claude Code. */
40
+ /** Codex events that harnery uses from the current native lifecycle surface. */
41
41
  export const CODEX_EVENTS = [
42
42
  { settingsKey: "SessionStart", subcommand: "session-start" },
43
- { settingsKey: "SessionEnd", subcommand: "session-end" },
44
43
  { settingsKey: "PreToolUse", subcommand: "pre-tool-use" },
45
44
  { settingsKey: "PostToolUse", subcommand: "post-tool-use" },
46
- { settingsKey: "PostToolUseFailure", subcommand: "post-tool-use-failure" },
47
45
  { settingsKey: "UserPromptSubmit", subcommand: "user-prompt-submit" },
48
46
  { settingsKey: "SubagentStart", subcommand: "sub-agent-start" },
49
47
  { settingsKey: "SubagentStop", subcommand: "sub-agent-stop" },
50
48
  { settingsKey: "Stop", subcommand: "stop" },
49
+ ];
50
+ /** Entries written by harnery before Codex adopted a strict native hook schema. */
51
+ export const LEGACY_CODEX_EVENTS = [
52
+ { settingsKey: "SessionEnd", subcommand: "session-end" },
53
+ { settingsKey: "PostToolUseFailure", subcommand: "post-tool-use-failure" },
51
54
  { settingsKey: "StopFailure", subcommand: "stop-failure" },
52
55
  ];
56
+ /** Every hook key accepted by Codex 0.144, including events harnery does not consume. */
57
+ export const CODEX_ALLOWED_EVENT_KEYS = [
58
+ "SessionStart",
59
+ "PreToolUse",
60
+ "PermissionRequest",
61
+ "PostToolUse",
62
+ "PreCompact",
63
+ "PostCompact",
64
+ "UserPromptSubmit",
65
+ "SubagentStart",
66
+ "SubagentStop",
67
+ "Stop",
68
+ ];
53
69
  /** Every supported harness, fully wireable by `harn init`. */
54
70
  export const HARNESS_SPECS = {
55
71
  "claude-code": {
@@ -67,5 +83,8 @@ export const HARNESS_SPECS = {
67
83
  settingsFile: ".codex/hooks.json",
68
84
  events: CODEX_EVENTS,
69
85
  entryShape: "claude",
86
+ legacyEvents: LEGACY_CODEX_EVENTS,
87
+ allowedTopLevelKeys: ["description", "hooks"],
88
+ allowedEventKeys: CODEX_ALLOWED_EVENT_KEYS,
70
89
  },
71
90
  };
@@ -28,6 +28,7 @@ export interface CursorHookGroup {
28
28
  export type HookGroup = ClaudeHookGroup | CursorHookGroup;
29
29
  export interface SettingsFile {
30
30
  version?: number;
31
+ description?: string;
31
32
  hooks?: Record<string, HookGroup[]>;
32
33
  [k: string]: unknown;
33
34
  }
@@ -51,6 +52,10 @@ export interface WiringDiff {
51
52
  * these — they need explicit removal — so they're surfaced separately.
52
53
  */
53
54
  orphans: string[];
55
+ /** Settings fields rejected by a harness with a strict top-level schema. */
56
+ invalidTopLevelKeys: string[];
57
+ /** Hook event names rejected by a harness with a strict event schema. */
58
+ invalidEventKeys: string[];
54
59
  }
55
60
  /**
56
61
  * Pure diff of one settings object against one harness spec. Read-only inverse
@@ -63,6 +68,9 @@ export interface HarnessWiringStatus {
63
68
  settingsFile: string;
64
69
  missing: HookEvent[];
65
70
  orphans: string[];
71
+ invalidTopLevelKeys: string[];
72
+ invalidEventKeys: string[];
73
+ parseError?: string;
66
74
  }
67
75
  /**
68
76
  * Inspect every harness whose settings file exists under `projectRoot` and
@@ -1 +1 @@
1
- {"version":3,"file":"wiring.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/harness/wiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,aAAa,CAAC;AAErB,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC5C;AACD,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,MAAM,SAAS,GAAG,eAAe,GAAG,eAAe,CAAC;AAE1D,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IACpC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAED,iDAAiD;AACjD,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAE3E;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,EAAE,CAMxD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAEnF;AAQD,MAAM,WAAW,UAAU;IACzB,kDAAkD;IAClD,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,iCAAiC;IACjC,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB;;;;OAIG;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CA0BhF;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,SAAS,CAAC;IACnB,wDAAwD;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAwB5E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAkBvC"}
1
+ {"version":3,"file":"wiring.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/harness/wiring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,aAAa,CAAC;AAErB,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC5C;AACD,0CAA0C;AAC1C,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,MAAM,SAAS,GAAG,eAAe,GAAG,eAAe,CAAC;AAE1D,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IACpC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAED,iDAAiD;AACjD,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAE3E;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,EAAE,CAMxD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAEnF;AAQD,MAAM,WAAW,UAAU;IACzB,kDAAkD;IAClD,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,iCAAiC;IACjC,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB;;;;OAIG;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,4EAA4E;IAC5E,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,yEAAyE;IACzE,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CA2ChF;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,SAAS,CAAC;IACnB,wDAAwD;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,mBAAmB,EAAE,CAwC5E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAkBvC"}
@@ -65,7 +65,23 @@ export function diffWiring(settings, spec) {
65
65
  }
66
66
  }
67
67
  }
68
- return { missing, present, orphans: [...orphans].sort() };
68
+ const invalidTopLevelKeys = spec.allowedTopLevelKeys
69
+ ? Object.keys(settings)
70
+ .filter((key) => !spec.allowedTopLevelKeys.includes(key))
71
+ .sort()
72
+ : [];
73
+ const invalidEventKeys = spec.allowedEventKeys
74
+ ? Object.keys(hooks)
75
+ .filter((key) => !spec.allowedEventKeys.includes(key))
76
+ .sort()
77
+ : [];
78
+ return {
79
+ missing,
80
+ present,
81
+ orphans: [...orphans].sort(),
82
+ invalidTopLevelKeys,
83
+ invalidEventKeys,
84
+ };
69
85
  }
70
86
  /**
71
87
  * Inspect every harness whose settings file exists under `projectRoot` and
@@ -88,21 +104,34 @@ export function loadHarnessWiring(projectRoot) {
88
104
  try {
89
105
  settings = JSON.parse(readFileSync(settingsPath, "utf8"));
90
106
  }
91
- catch {
92
- // Unparseable settings file: can't tell opt-in from noise, and the
93
- // harness itself will complain about its own malformed config. Skip.
107
+ catch (error) {
108
+ out.push({
109
+ harness: id,
110
+ settingsFile: spec.settingsFile,
111
+ missing: [],
112
+ orphans: [],
113
+ invalidTopLevelKeys: [],
114
+ invalidEventKeys: [],
115
+ parseError: error.message,
116
+ });
94
117
  continue;
95
118
  }
96
119
  const diff = diffWiring(settings, spec);
97
120
  if (diff.present.length === 0)
98
121
  continue; // not opted in → not drift
99
- if (diff.missing.length === 0 && diff.orphans.length === 0)
122
+ if (diff.missing.length === 0 &&
123
+ diff.orphans.length === 0 &&
124
+ diff.invalidTopLevelKeys.length === 0 &&
125
+ diff.invalidEventKeys.length === 0) {
100
126
  continue; // current
127
+ }
101
128
  out.push({
102
129
  harness: id,
103
130
  settingsFile: spec.settingsFile,
104
131
  missing: diff.missing,
105
132
  orphans: diff.orphans,
133
+ invalidTopLevelKeys: diff.invalidTopLevelKeys,
134
+ invalidEventKeys: diff.invalidEventKeys,
106
135
  });
107
136
  }
108
137
  return out;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/scratch/index.ts"],"names":[],"mappings":"AAcA;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,kBAAkB,iFAQrB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,eAAO,MAAM,iBAAiB,QAAY,CAAC;AAC3C,eAAO,MAAM,kBAAkB,QAAY,CAAC;AAC5C,eAAO,MAAM,sBAAsB,IAAI,CAAC;AAExC,6DAA6D;AAC7D,eAAO,MAAM,eAAe,QACyF,CAAC;AAEtH,mFAAmF;AACnF,eAAO,MAAM,wBAAwB,yBAAyB,CAAC;AAE/D,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,eAAe,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;CACf;AAID,wBAAgB,UAAU,IAAI,MAAM,CAMnC;AAED,wBAAgB,UAAU,IAAI,MAAM,CAInC;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEtD;AAeD,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,CAAC,GAAE,IAAiB,GAAG,MAAM,CAW1D;AAID,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CA+DtE;AAED,+CAA+C;AAC/C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM,CAkBxD;AAmBD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,SAAoB,GAAG,SAAS,EAAE,CA2DrF;AAID,wBAAgB,WAAW,CACzB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,MAAM,GACX,UAAU,CAgEZ;AAID,wGAAwG;AACxG,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOhE;AAED,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,IAAI,SAAyB,GAAG,MAAM,CAkBnE;AAED,uFAAuF;AACvF,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAajD;AAED,qDAAqD;AACrD,wBAAgB,YAAY,IAAI;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,EAAE,CAWF;AAID,wBAAgB,mBAAmB,IAAI,MAAM,CAM5C;AAED,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAIjE;AAED,2FAA2F;AAC3F,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoB9D"}
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
2
2
  import { dirname, resolve } from "node:path";
3
- import { monorepoRoot, readHeartbeat, resolveOwner } from "../../core/agents/index.js";
4
- import { resolveMachineLabel } from "../machine.js";
3
+ import { resolveMachineLabel } from "../../lib/machine.js";
4
+ import { monorepoRoot, readHeartbeat, resolveOwner } from "../agents/index.js";
5
5
  /**
6
6
  * Agent scratchpad: per-agent markdown journal at `.harnery/scratch/<instance_id>.md`.
7
7
  *
@@ -0,0 +1,178 @@
1
+ /**
2
+ * `devtools`: read locally-stored state of the AI coding agents harnery
3
+ * supports — Claude Code (`~/.claude`), Codex (`~/.codex`), and Cursor
4
+ * (`~/.cursor`) — into one uniform status shape: login state, plan/tier,
5
+ * auth expiry, session counts, and (where the tool stores them locally)
6
+ * rate-limit / quota windows.
7
+ *
8
+ * Everything here reads files on disk — no network, no vendor API, no
9
+ * credentials leave the machine (auth tokens are inspected for their
10
+ * non-secret claims only; the token strings themselves are never returned).
11
+ * The signals a tool keeps server-side (Cursor usage + billing, Claude's live
12
+ * rate-limit windows) surface as `null` with a note rather than a guess.
13
+ *
14
+ * Pure toolkit tier: depends only on `node:*`, never on `src/core/`.
15
+ */
16
+ export type DevtoolName = "claude-code" | "codex" | "cursor";
17
+ export interface QuotaWindow {
18
+ /** Human label for the reset window (e.g. "5h", "weekly", "45m"). */
19
+ window: string;
20
+ /** Percent of the window's allowance consumed, 0-100, or null if unknown. */
21
+ usedPercent: number | null;
22
+ /** ISO timestamp when the window resets, or null if unknown. */
23
+ resetsAt: string | null;
24
+ }
25
+ export interface ApiEnrichment {
26
+ /** The configured API key authenticated successfully. */
27
+ ok: boolean;
28
+ /** Human name the vendor reports for the key (e.g. Cursor's apiKeyName). */
29
+ keyName: string | null;
30
+ /** Cloud-agent activity (Cursor Cloud Agent API), when available. */
31
+ cloudAgents: {
32
+ total: number;
33
+ active: number;
34
+ } | null;
35
+ /** Error message when the key is configured but a call failed. */
36
+ error: string | null;
37
+ }
38
+ /**
39
+ * Cursor billing-cycle + usage snapshot, fetched from cursor.com's own dashboard
40
+ * API using the IDE's locally-stored session token — the same call Cursor's UI
41
+ * makes for the Spending page. No API key to mint; it reads what's already on
42
+ * disk. Percentages are 0-100; cent amounts are raw (divide by 100 for dollars).
43
+ */
44
+ export interface CursorUsage {
45
+ /** ISO start of the current billing cycle. */
46
+ cycleStart: string | null;
47
+ /** ISO end of the current billing cycle (the "resets on …" date). */
48
+ cycleEnd: string | null;
49
+ /** Percent of included total usage consumed this cycle (Cursor's "Total"). */
50
+ totalPercentUsed: number | null;
51
+ /** Percent of included API usage consumed (named-model / "API"). */
52
+ apiPercentUsed: number | null;
53
+ /** Percent of included first-party ("Auto") model usage consumed. */
54
+ firstPartyPercentUsed: number | null;
55
+ /** Included usage allowance in cents (e.g. 7000 = $70). */
56
+ includedLimitCents: number | null;
57
+ }
58
+ /**
59
+ * Overage / on-demand dollar spend against a cap — Cursor's on-demand usage and
60
+ * Claude's extra-usage credits are the same idea. Cents are raw (÷100 for USD).
61
+ */
62
+ export interface SpendStatus {
63
+ /** Human label for what this spend covers (e.g. "On-demand", "Extra usage"). */
64
+ label: string;
65
+ /** Amount spent this cycle in cents. */
66
+ usedCents: number | null;
67
+ /** Spend cap in cents, or null when unset/unlimited. */
68
+ limitCents: number | null;
69
+ }
70
+ export interface ToolStatus {
71
+ tool: DevtoolName;
72
+ /** The tool's config directory exists on this machine. */
73
+ installed: boolean;
74
+ /** A credential is present and not obviously expired. null when undeterminable. */
75
+ loggedIn: boolean | null;
76
+ /** Account identifier (email where the tool exposes one), else null. */
77
+ account: string | null;
78
+ /** Plan / seat tier as the tool records it locally (e.g. "team", "team_tier_1"). */
79
+ plan: string | null;
80
+ /** Rate-limit tier string where the tool records one, else null. */
81
+ rateLimitTier: string | null;
82
+ /** ISO expiry of the active access credential, else null. */
83
+ authExpiresAt: string | null;
84
+ /** Count of local session transcripts, else null. */
85
+ sessions: number | null;
86
+ /** ISO timestamp of the most recent local session activity, else null. */
87
+ lastActivity: string | null;
88
+ /** Locally-known quota/rate-limit windows, or null when the tool keeps them server-side. */
89
+ quota: QuotaWindow[] | null;
90
+ /**
91
+ * Total tokens observed in local transcripts within the scan window
92
+ * (`windowDays`). Codex always reports it — its per-session cumulative total
93
+ * is one tail-read per rollout, cheap enough for every render. Claude Code's
94
+ * is `--usage`-gated (a full transcript scan, potentially gigabytes) and null
95
+ * otherwise. Cursor keeps token counts server-side, so it stays null.
96
+ */
97
+ tokensUsed: number | null;
98
+ /**
99
+ * Result of the optional API enrichment (network), populated by
100
+ * `enrichFromApi` when a key is configured. `null` when no enrichment ran.
101
+ */
102
+ api: ApiEnrichment | null;
103
+ /**
104
+ * Cursor billing-cycle + usage, populated by `enrichFromApi` from the IDE's
105
+ * own session token (no API key needed). `null` when no enrichment ran, the
106
+ * token is stale, or the tool isn't Cursor.
107
+ */
108
+ usage: CursorUsage | null;
109
+ /**
110
+ * Overage / on-demand dollar spend, populated by `enrichFromApi` (Cursor's
111
+ * on-demand, Claude's extra-usage credits). `null` when no enrichment ran or
112
+ * the plan has no overage concept.
113
+ */
114
+ spend: SpendStatus | null;
115
+ /** Caveats about what is and isn't derivable locally for this tool. */
116
+ notes: string[];
117
+ }
118
+ export interface DevtoolsReport {
119
+ generatedAt: string;
120
+ windowDays: number | null;
121
+ tools: ToolStatus[];
122
+ }
123
+ /** One endpoint's health, from `probeEndpoints` (the `devtools doctor` check). */
124
+ export interface ProbeResult {
125
+ tool: DevtoolName;
126
+ endpoint: string;
127
+ /** Client version we put in the request's User-Agent, else null. */
128
+ clientVersion: string | null;
129
+ /** HTTP status, or null when no call was made / the request threw. */
130
+ status: number | null;
131
+ outcome: "ok" | "rate_limited" | "auth_rejected" | "shape_changed" | "unreachable" | "no_credential";
132
+ detail: string;
133
+ }
134
+ export interface ReadDevtoolsOpts {
135
+ /** Home directory to resolve tool config against. Defaults to os.homedir(). */
136
+ home?: string;
137
+ /** Scan transcripts for token totals (opt-in; can be slow). Default false. */
138
+ usage?: boolean;
139
+ /** When scanning usage, only include transcripts modified within N days. Default 7. */
140
+ windowDays?: number;
141
+ /** Clock injection for tests. Default Date.now(). */
142
+ now?: number;
143
+ /** Restrict to a subset of tools. Default all three. */
144
+ only?: readonly DevtoolName[];
145
+ }
146
+ /** Path of the machine-local Cursor API key file (honors XDG_CONFIG_HOME). */
147
+ export declare function cursorApiKeyPath(): string;
148
+ /** Resolve a Cursor API key: env `CURSOR_API_KEY` first, then the key file. */
149
+ export declare function resolveCursorApiKey(): string | null;
150
+ /**
151
+ * Enrich a report's entries over the network with live usage each tool keeps
152
+ * server-side, authenticating with the credential already on disk. Best-effort
153
+ * and network-guarded: every failure degrades to a note, never throws. No-op
154
+ * for a tool that isn't installed / present in the report.
155
+ */
156
+ export declare function enrichFromApi(report: DevtoolsReport, opts?: {
157
+ cursorKey?: string | null;
158
+ timeoutMs?: number;
159
+ home?: string;
160
+ /** Cache TTL for the usage endpoints (ms). 0 disables caching. Default 120s. */
161
+ cacheTtlMs?: number;
162
+ }): Promise<DevtoolsReport>;
163
+ /**
164
+ * One live call per usage endpoint (cache-bypassing) to check the integration
165
+ * still works — the occasional "did a client change its headers?" test. It
166
+ * exercises the EXACT request builders production uses, so an `auth_rejected`
167
+ * result means our headers/token stopped being accepted, and `shape_changed`
168
+ * means the response schema drifted. Rate-limited results are reported as such,
169
+ * not as failures. Makes at most one request per tool; run it by hand (it is not
170
+ * scheduled) so it can't itself cause a rate limit.
171
+ */
172
+ export declare function probeEndpoints(opts?: {
173
+ home?: string;
174
+ timeoutMs?: number;
175
+ only?: readonly DevtoolName[];
176
+ }): Promise<ProbeResult[]>;
177
+ export declare function readDevtools(opts?: ReadDevtoolsOpts): DevtoolsReport;
178
+ //# sourceMappingURL=devtools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devtools.d.ts","sourceRoot":"","sources":["../../src/lib/devtools.ts"],"names":[],"mappings":"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"}