@indigoai-us/hq-cli 5.192.2 → 5.193.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
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.193.0] — 2026-09-25
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `hq secrets set` now handles a closed stdin descriptor during terminal cleanup.
|
|
10
|
+
|
|
11
|
+
### Improved
|
|
12
|
+
|
|
13
|
+
- Sentry reports for failed skill-wrapper replacement now include bounded ownership-state tags.
|
|
14
|
+
|
|
5
15
|
## [5.192.2] — 2026-09-25
|
|
6
16
|
|
|
7
17
|
### Fixed
|
package/dist/commands/secrets.js
CHANGED
|
@@ -188,12 +188,23 @@ function promptSecretInteractively() {
|
|
|
188
188
|
let value = "";
|
|
189
189
|
process.stdin.setEncoding("utf8");
|
|
190
190
|
const cleanup = () => {
|
|
191
|
-
|
|
192
|
-
process.stdin.
|
|
191
|
+
try {
|
|
192
|
+
if (process.stdin.isTTY) {
|
|
193
|
+
try {
|
|
194
|
+
process.stdin.setRawMode(false);
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
if (error?.code !== "EBADF") {
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
process.stdin.removeListener("data", onData);
|
|
205
|
+
process.stdin.removeListener("end", onEnd);
|
|
206
|
+
process.stdin.pause();
|
|
193
207
|
}
|
|
194
|
-
process.stdin.removeListener("data", onData);
|
|
195
|
-
process.stdin.removeListener("end", onEnd);
|
|
196
|
-
process.stdin.pause();
|
|
197
208
|
};
|
|
198
209
|
const onEnd = () => {
|
|
199
210
|
cleanup();
|
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
export interface CompanySkillWrapperOwnershipDiagnostics {
|
|
3
|
+
kind: "directory" | "symlink" | "file" | "other";
|
|
4
|
+
bridgeState: "all_live" | "partial" | "none" | "empty" | "not_applicable";
|
|
5
|
+
markerState: "bound" | "missing" | "invalid" | "unbound" | "unavailable" | "not_applicable";
|
|
6
|
+
linkTarget: "company_skills" | "outside_company_skills" | "unavailable" | "not_applicable";
|
|
7
|
+
platform: "win32" | "darwin" | "linux" | "other";
|
|
8
|
+
nameEncoding: "percent_encoded" | "literal";
|
|
9
|
+
}
|
|
10
|
+
export declare function companySkillWrapperSentryTagsFor(error: unknown): Record<string, string> | undefined;
|
|
2
11
|
export interface SurfaceCompanySkillInput {
|
|
3
12
|
hqRoot: string;
|
|
4
13
|
companySlug: string;
|
|
@@ -62,20 +62,6 @@ function symlinkTarget(linkPath) {
|
|
|
62
62
|
function generatedMarkerPath(wrapperPath) {
|
|
63
63
|
return path.join(path.dirname(wrapperPath), WRAPPER_MARKER_DIR, `${path.basename(wrapperPath)}.json`);
|
|
64
64
|
}
|
|
65
|
-
function hasBoundGeneratedMarker(wrapperPath, companySlug) {
|
|
66
|
-
try {
|
|
67
|
-
const marker = JSON.parse(fs.readFileSync(generatedMarkerPath(wrapperPath), "utf8"));
|
|
68
|
-
const wrapper = fs.lstatSync(wrapperPath);
|
|
69
|
-
return (marker.version === 2 &&
|
|
70
|
-
marker.companySlug === companySlug &&
|
|
71
|
-
marker.wrapperDev === wrapper.dev &&
|
|
72
|
-
marker.wrapperIno === wrapper.ino &&
|
|
73
|
-
marker.wrapperBirthtimeMs === wrapper.birthtimeMs);
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
65
|
function writeGeneratedMarker(wrapperPath, companySlug, skillSlug) {
|
|
80
66
|
const markerPath = generatedMarkerPath(wrapperPath);
|
|
81
67
|
try {
|
|
@@ -104,21 +90,116 @@ function removeGeneratedMarker(wrapperPath) {
|
|
|
104
90
|
// never sufficient to surface content on its own.
|
|
105
91
|
}
|
|
106
92
|
}
|
|
107
|
-
|
|
93
|
+
const COMPANY_SKILL_WRAPPER_DIAGNOSTICS = Symbol("company-skill-wrapper-diagnostics");
|
|
94
|
+
function companySkillWrapperOwnershipError(wrapperPath, diagnostics) {
|
|
95
|
+
const error = new Error(`Cannot replace '${wrapperPath}' because it is not an HQ-generated skill wrapper.`);
|
|
96
|
+
Object.defineProperty(error, COMPANY_SKILL_WRAPPER_DIAGNOSTICS, {
|
|
97
|
+
value: diagnostics,
|
|
98
|
+
});
|
|
99
|
+
return error;
|
|
100
|
+
}
|
|
101
|
+
export function companySkillWrapperSentryTagsFor(error) {
|
|
102
|
+
if (!(error instanceof Error))
|
|
103
|
+
return undefined;
|
|
104
|
+
const diagnostics = error[COMPANY_SKILL_WRAPPER_DIAGNOSTICS];
|
|
105
|
+
if (!diagnostics)
|
|
106
|
+
return undefined;
|
|
107
|
+
return {
|
|
108
|
+
hq_cli_skill_wrapper_kind: diagnostics.kind,
|
|
109
|
+
hq_cli_skill_wrapper_bridge_state: diagnostics.bridgeState,
|
|
110
|
+
hq_cli_skill_wrapper_marker_state: diagnostics.markerState,
|
|
111
|
+
hq_cli_skill_wrapper_link_target: diagnostics.linkTarget,
|
|
112
|
+
hq_cli_skill_wrapper_platform: diagnostics.platform,
|
|
113
|
+
hq_cli_skill_wrapper_name_encoding: diagnostics.nameEncoding,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function inspectGeneratedNamespacedWrapper(wrapperPath, hqRoot, companySlug) {
|
|
108
117
|
const companySkillsRoot = path.join(hqRoot, "companies", companySlug, "skills");
|
|
109
|
-
const
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
118
|
+
const wrapperEntry = lstat(wrapperPath);
|
|
119
|
+
if (wrapperEntry?.isSymbolicLink()) {
|
|
120
|
+
const target = symlinkTarget(wrapperPath);
|
|
121
|
+
const linkTarget = target === undefined
|
|
122
|
+
? "unavailable"
|
|
123
|
+
: isWithin(companySkillsRoot, target)
|
|
124
|
+
? "company_skills"
|
|
125
|
+
: "outside_company_skills";
|
|
126
|
+
return {
|
|
127
|
+
owned: linkTarget === "company_skills",
|
|
128
|
+
diagnostics: {
|
|
129
|
+
kind: "symlink",
|
|
130
|
+
bridgeState: "not_applicable",
|
|
131
|
+
markerState: "not_applicable",
|
|
132
|
+
linkTarget,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (!wrapperEntry?.isDirectory()) {
|
|
137
|
+
return {
|
|
138
|
+
owned: false,
|
|
139
|
+
diagnostics: {
|
|
140
|
+
kind: wrapperEntry?.isFile() ? "file" : "other",
|
|
141
|
+
bridgeState: "not_applicable",
|
|
142
|
+
markerState: "not_applicable",
|
|
143
|
+
linkTarget: "not_applicable",
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
115
147
|
const entries = fs.readdirSync(wrapperPath).filter((entry) => !entry.startsWith("."));
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
148
|
+
const liveBridgeCount = entries.reduce((count, entry) => {
|
|
149
|
+
const target = symlinkTarget(path.join(wrapperPath, entry));
|
|
150
|
+
return count + (target !== undefined && isWithin(companySkillsRoot, target) ? 1 : 0);
|
|
151
|
+
}, 0);
|
|
152
|
+
const bridgeState = entries.length === 0
|
|
153
|
+
? "empty"
|
|
154
|
+
: liveBridgeCount === entries.length
|
|
155
|
+
? "all_live"
|
|
156
|
+
: liveBridgeCount > 0
|
|
157
|
+
? "partial"
|
|
158
|
+
: "none";
|
|
159
|
+
const markerState = generatedMarkerState(wrapperPath, companySlug);
|
|
160
|
+
return {
|
|
161
|
+
owned: bridgeState === "all_live" || markerState === "bound",
|
|
162
|
+
diagnostics: {
|
|
163
|
+
kind: "directory",
|
|
164
|
+
bridgeState,
|
|
165
|
+
markerState,
|
|
166
|
+
linkTarget: "not_applicable",
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function isGeneratedNamespacedWrapper(wrapperPath, hqRoot, companySlug) {
|
|
171
|
+
return inspectGeneratedNamespacedWrapper(wrapperPath, hqRoot, companySlug).owned;
|
|
172
|
+
}
|
|
173
|
+
function generatedMarkerState(wrapperPath, companySlug) {
|
|
174
|
+
let markerContents;
|
|
175
|
+
try {
|
|
176
|
+
markerContents = fs.readFileSync(generatedMarkerPath(wrapperPath), "utf8");
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT"
|
|
180
|
+
? "missing"
|
|
181
|
+
: "unavailable";
|
|
182
|
+
}
|
|
183
|
+
let marker;
|
|
184
|
+
try {
|
|
185
|
+
marker = JSON.parse(markerContents);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return "invalid";
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
const wrapper = fs.lstatSync(wrapperPath);
|
|
192
|
+
return marker.version === 2 &&
|
|
193
|
+
marker.companySlug === companySlug &&
|
|
194
|
+
marker.wrapperDev === wrapper.dev &&
|
|
195
|
+
marker.wrapperIno === wrapper.ino &&
|
|
196
|
+
marker.wrapperBirthtimeMs === wrapper.birthtimeMs
|
|
197
|
+
? "bound"
|
|
198
|
+
: "unbound";
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return "unavailable";
|
|
202
|
+
}
|
|
122
203
|
}
|
|
123
204
|
function isGeneratedLegacyEntry(legacyPath, sourceDir, skillFile) {
|
|
124
205
|
const target = symlinkTarget(legacyPath);
|
|
@@ -224,8 +305,19 @@ export function surfaceCompanySkill(input) {
|
|
|
224
305
|
const backupPath = `${stagingPath}.previous`;
|
|
225
306
|
let previousMoved = false;
|
|
226
307
|
if (currentWrapper) {
|
|
227
|
-
|
|
228
|
-
|
|
308
|
+
const ownership = inspectGeneratedNamespacedWrapper(wrapperPath, input.hqRoot, input.companySlug);
|
|
309
|
+
if (!ownership.owned) {
|
|
310
|
+
const runtimePlatform = process.platform;
|
|
311
|
+
const platform = win32
|
|
312
|
+
? "win32"
|
|
313
|
+
: runtimePlatform === "darwin" || runtimePlatform === "linux"
|
|
314
|
+
? runtimePlatform
|
|
315
|
+
: "other";
|
|
316
|
+
throw companySkillWrapperOwnershipError(wrapperPath, {
|
|
317
|
+
...ownership.diagnostics,
|
|
318
|
+
platform,
|
|
319
|
+
nameEncoding: win32 ? "percent_encoded" : "literal",
|
|
320
|
+
});
|
|
229
321
|
}
|
|
230
322
|
renameEntry(wrapperPath, backupPath);
|
|
231
323
|
previousMoved = true;
|
package/dist/sentry.js
CHANGED
|
@@ -5,6 +5,7 @@ import { beforeBreadcrumb } from "./utils/breadcrumb-buffer.js";
|
|
|
5
5
|
import { CLI_VERSION } from "./cli-version.js";
|
|
6
6
|
import { getCachedSentryUser } from "./utils/sentry-identity.js";
|
|
7
7
|
import { isEpipe } from "./utils/epipe.js";
|
|
8
|
+
import { companySkillWrapperSentryTagsFor } from "./lib/company-skill-wrapper.js";
|
|
8
9
|
import { environmentalFsErrorMessage } from "./utils/environmental-error.js";
|
|
9
10
|
import { incompleteInstallCaptureContext, incompleteInstallMessage, } from "./utils/incomplete-install-error.js";
|
|
10
11
|
import { installTreeTornSentryTagsFor, sentryFingerprintFor, sentryMcpCollisionContextFor, } from "./utils/sentry-fingerprint.js";
|
|
@@ -84,6 +85,10 @@ export function epipeAwareBeforeSend(event, hint) {
|
|
|
84
85
|
if (fingerprint)
|
|
85
86
|
event.fingerprint = fingerprint;
|
|
86
87
|
}
|
|
88
|
+
const skillWrapperTags = companySkillWrapperSentryTagsFor(hint?.originalException);
|
|
89
|
+
if (skillWrapperTags) {
|
|
90
|
+
event.tags = { ...event.tags, ...skillWrapperTags };
|
|
91
|
+
}
|
|
87
92
|
const installTreeTags = installTreeTornSentryTagsFor(hint?.originalException);
|
|
88
93
|
if (installTreeTags) {
|
|
89
94
|
event.tags = { ...event.tags, ...installTreeTags };
|
|
@@ -99,5 +99,5 @@ export declare const CONTRIBUTION_KEYS: ContributionKey[];
|
|
|
99
99
|
*/
|
|
100
100
|
export declare function payloadFor(key: ContributionKey, item: string): string;
|
|
101
101
|
/** Keys whose contributions are wired by a host symlink (skip `merge` rows). */
|
|
102
|
-
export declare const SYMLINK_KEYS: ("workers" | "mcp" | "
|
|
102
|
+
export declare const SYMLINK_KEYS: ("workers" | "mcp" | "skills" | "knowledge" | "commands" | "hooks" | "policies" | "scripts")[];
|
|
103
103
|
//# sourceMappingURL=contribution-table.d.ts.map
|