@hasna/instructions 0.5.6 → 0.6.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/README.md +64 -20
- package/dist/cli/fail-closed-no-env.test.d.ts +2 -0
- package/dist/cli/fail-closed-no-env.test.d.ts.map +1 -0
- package/dist/cli/index.js +1711 -422
- package/dist/data/config-store.d.ts +62 -33
- package/dist/data/config-store.d.ts.map +1 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/generated/storage-kit/backend.d.ts +4 -4
- package/dist/generated/storage-kit/backend.d.ts.map +1 -1
- package/dist/generated/storage-kit/index.d.ts +1 -1
- package/dist/generated/storage-kit/index.d.ts.map +1 -1
- package/dist/generated/storage-kit/migrations.d.ts +21 -0
- package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
- package/dist/generated/storage-kit/pool.d.ts +2 -5
- package/dist/generated/storage-kit/pool.d.ts.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2486 -386
- package/dist/lib/app-home.d.ts +9 -0
- package/dist/lib/app-home.d.ts.map +1 -1
- package/dist/lib/client-types.d.ts +130 -0
- package/dist/lib/client-types.d.ts.map +1 -0
- package/dist/lib/client-types.test.d.ts +2 -0
- package/dist/lib/client-types.test.d.ts.map +1 -0
- package/dist/lib/local-opt-in.d.ts +82 -0
- package/dist/lib/local-opt-in.d.ts.map +1 -0
- package/dist/lib/project-context.d.ts +14 -14
- package/dist/lib/project-context.d.ts.map +1 -1
- package/dist/lib/project-dashboard-standard.d.ts +1 -1
- package/dist/lib/project-dashboard-standard.d.ts.map +1 -1
- package/dist/lib/session-render-state-hermeticity.test.d.ts +2 -0
- package/dist/lib/session-render-state-hermeticity.test.d.ts.map +1 -0
- package/dist/lib/session-render-state.d.ts +9 -0
- package/dist/lib/session-render-state.d.ts.map +1 -1
- package/dist/lib/transport-resolver.d.ts +80 -0
- package/dist/lib/transport-resolver.d.ts.map +1 -0
- package/dist/lib/transport-resolver.test.d.ts +2 -0
- package/dist/lib/transport-resolver.test.d.ts.map +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1439 -195
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/sdk/index.d.ts +22 -0
- package/dist/sdk/index.d.ts.map +1 -0
- package/dist/sdk/index.js +1042 -0
- package/dist/sdk/resolve.d.ts +82 -0
- package/dist/sdk/resolve.d.ts.map +1 -0
- package/dist/sdk/resolve.test.d.ts +2 -0
- package/dist/sdk/resolve.test.d.ts.map +1 -0
- package/dist/sdk/sdk-bundle-self-contained.test.d.ts +2 -0
- package/dist/sdk/sdk-bundle-self-contained.test.d.ts.map +1 -0
- package/dist/sdk/v1.generated.d.ts +288 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -0
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +55 -64
- package/dist/test-support/preload-state-home.d.ts +2 -0
- package/dist/test-support/preload-state-home.d.ts.map +1 -0
- package/package.json +12 -12
- package/dashboard/README.md +0 -37
- package/dist/lib/retired-storage-mode.d.ts +0 -8
- package/dist/lib/retired-storage-mode.d.ts.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -1290,8 +1290,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1290
1290
|
args = args.slice();
|
|
1291
1291
|
let launchWithNode = false;
|
|
1292
1292
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1293
|
-
function findFile(
|
|
1294
|
-
const localBin = path.resolve(
|
|
1293
|
+
function findFile(baseDir, baseName) {
|
|
1294
|
+
const localBin = path.resolve(baseDir, baseName);
|
|
1295
1295
|
if (fs.existsSync(localBin))
|
|
1296
1296
|
return localBin;
|
|
1297
1297
|
if (sourceExt.includes(path.extname(baseName)))
|
|
@@ -2136,119 +2136,167 @@ var init_types = __esm(() => {
|
|
|
2136
2136
|
};
|
|
2137
2137
|
});
|
|
2138
2138
|
|
|
2139
|
-
//
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
return key;
|
|
2144
|
-
}
|
|
2145
|
-
return null;
|
|
2139
|
+
// ../contracts/dist/client/transport.js
|
|
2140
|
+
import { createRequire } from "module";
|
|
2141
|
+
function envToken(name) {
|
|
2142
|
+
return name.toUpperCase().replace(/-/g, "_");
|
|
2146
2143
|
}
|
|
2147
|
-
function
|
|
2148
|
-
const
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2144
|
+
function clientTransportEnvKeys(name) {
|
|
2145
|
+
const envSegment = envToken(name);
|
|
2146
|
+
return {
|
|
2147
|
+
apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
|
|
2148
|
+
apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
function credentialOverrideEnvKey(name) {
|
|
2152
|
+
return `HASNA_${envToken(name)}_API_KEY_OVERRIDE`;
|
|
2153
|
+
}
|
|
2154
|
+
function credentialPointerEnvKey(name) {
|
|
2155
|
+
return `HASNA_${envToken(name)}_API_KEY_REF`;
|
|
2156
|
+
}
|
|
2157
|
+
var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", MAX_CREDENTIAL_FILE_BYTES, INSPECT_CUSTOM, CREDENTIAL_SEAL, AMBIENT_ENVIRONMENT, SECRETS_PACKAGE_SPECIFIER, requireSecretsSdk, IDEMPOTENT_METHODS, AUTHORITY_OVERRIDE_HEADERS;
|
|
2158
|
+
var init_transport = __esm(() => {
|
|
2159
|
+
MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
|
|
2160
|
+
INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
|
|
2161
|
+
CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
|
|
2162
|
+
AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
2163
|
+
SECRETS_PACKAGE_SPECIFIER = "@hasna/" + "secrets";
|
|
2164
|
+
requireSecretsSdk = createRequire(import.meta.url);
|
|
2165
|
+
IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
2166
|
+
AUTHORITY_OVERRIDE_HEADERS = new Set([
|
|
2167
|
+
"host",
|
|
2168
|
+
":authority",
|
|
2169
|
+
"forwarded",
|
|
2170
|
+
"x-forwarded-host",
|
|
2171
|
+
"x-original-host"
|
|
2172
|
+
]);
|
|
2173
|
+
});
|
|
2174
|
+
|
|
2175
|
+
// src/lib/local-opt-in.ts
|
|
2176
|
+
function instructionsLocalModeNotice() {
|
|
2177
|
+
return `instructions: local mode \u2014 ${INSTRUCTIONS_LOCAL_OPT_IN_ENV_KEYS[0]}=1 selects the on-box SQLite store, ` + `and no hosted authority was configured. Set HASNA_INSTRUCTIONS_API_KEY (or add the Keychain item ` + `hasna.credentials.instructions.api-key, or write ~/.hasna/instructions/config/credentials) to go hosted.`;
|
|
2178
|
+
}
|
|
2179
|
+
function isInstructionsLocalOptIn(env = process.env) {
|
|
2180
|
+
return INSTRUCTIONS_LOCAL_OPT_IN_ENV_KEYS.some((key) => (env[key] ?? "").trim() === "1");
|
|
2181
|
+
}
|
|
2182
|
+
function instructionsAuthorityEnvKeys() {
|
|
2183
|
+
const keys = clientTransportEnvKeys("instructions");
|
|
2184
|
+
return [
|
|
2185
|
+
...keys.apiUrlKeys,
|
|
2186
|
+
...keys.apiKeyKeys,
|
|
2187
|
+
credentialOverrideEnvKey("instructions"),
|
|
2188
|
+
credentialPointerEnvKey("instructions"),
|
|
2189
|
+
CREDENTIAL_PROFILE_ENV_KEY
|
|
2160
2190
|
];
|
|
2191
|
+
}
|
|
2192
|
+
function hasInstructionsEnvAuthorityIntent(env = process.env) {
|
|
2193
|
+
return instructionsAuthorityEnvKeys().some((key) => (env[key] ?? "").trim() !== "");
|
|
2194
|
+
}
|
|
2195
|
+
function selectsInstructionsLocalStore(env = process.env) {
|
|
2196
|
+
return !hasInstructionsEnvAuthorityIntent(env) && isInstructionsLocalOptIn(env);
|
|
2197
|
+
}
|
|
2198
|
+
function instructionsResolverEnv(env) {
|
|
2199
|
+
const blanks = instructionsAuthorityEnvKeys().filter((key) => (key in env) && (env[key] ?? "").trim() === "");
|
|
2200
|
+
if (blanks.length === 0)
|
|
2201
|
+
return env;
|
|
2202
|
+
const next = { ...env };
|
|
2203
|
+
for (const key of blanks)
|
|
2204
|
+
delete next[key];
|
|
2205
|
+
return next;
|
|
2206
|
+
}
|
|
2207
|
+
function isAmbientInstructionsEnv(env) {
|
|
2208
|
+
if (typeof process !== "undefined" && env === process.env)
|
|
2209
|
+
return true;
|
|
2210
|
+
return env[CONTRACTS_AMBIENT_ENVIRONMENT] === true;
|
|
2211
|
+
}
|
|
2212
|
+
function instructionsResolverInputs(env, credentials = {}) {
|
|
2213
|
+
const normalised = instructionsResolverEnv(env);
|
|
2214
|
+
if (normalised === env)
|
|
2215
|
+
return { env: normalised, credentials };
|
|
2216
|
+
const keychain = { ...credentials.keychain };
|
|
2217
|
+
if (keychain.enabled === undefined && keychain.run === undefined) {
|
|
2218
|
+
keychain.enabled = isAmbientInstructionsEnv(env);
|
|
2219
|
+
}
|
|
2220
|
+
return { env: normalised, credentials: { ...credentials, keychain } };
|
|
2221
|
+
}
|
|
2222
|
+
var INSTRUCTIONS_LOCAL_OPT_IN_ENV_KEYS, CONTRACTS_AMBIENT_ENVIRONMENT;
|
|
2223
|
+
var init_local_opt_in = __esm(() => {
|
|
2224
|
+
init_transport();
|
|
2225
|
+
INSTRUCTIONS_LOCAL_OPT_IN_ENV_KEYS = ["HASNA_INSTRUCTIONS_LOCAL"];
|
|
2226
|
+
CONTRACTS_AMBIENT_ENVIRONMENT = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
2161
2227
|
});
|
|
2162
2228
|
|
|
2163
|
-
//
|
|
2164
|
-
import {
|
|
2165
|
-
import {
|
|
2166
|
-
|
|
2229
|
+
// src/lib/app-home.ts
|
|
2230
|
+
import { existsSync as existsSync3 } from "fs";
|
|
2231
|
+
import { homedir as homedir2 } from "os";
|
|
2232
|
+
import { join as join3, resolve as resolve2 } from "path";
|
|
2233
|
+
import { homedir as pathsResolverHomedir2 } from "os";
|
|
2234
|
+
import { join as pathsResolverJoin2 } from "path";
|
|
2235
|
+
function pathsResolverAssertApp2(app) {
|
|
2167
2236
|
if (typeof app !== "string" || app.length === 0) {
|
|
2168
2237
|
throw new TypeError("paths: app must be a non-empty string");
|
|
2169
2238
|
}
|
|
2170
|
-
if (!
|
|
2239
|
+
if (!PATHS_RESOLVER_APP_SLUG_RE2.test(app)) {
|
|
2171
2240
|
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
2172
2241
|
}
|
|
2173
2242
|
}
|
|
2174
|
-
function
|
|
2175
|
-
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
const value = envOf2(options)[KIND_ENV2[kind]];
|
|
2179
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
2180
|
-
}
|
|
2181
|
-
function isMacOS2(platform) {
|
|
2182
|
-
return platform === "darwin";
|
|
2243
|
+
function pathsResolverAssertKind2(kind) {
|
|
2244
|
+
if (!Object.keys(PATHS_RESOLVER_KIND_ENV2).includes(kind)) {
|
|
2245
|
+
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV2).join(", ")}`);
|
|
2246
|
+
}
|
|
2183
2247
|
}
|
|
2184
|
-
function
|
|
2185
|
-
|
|
2186
|
-
|
|
2248
|
+
function pathsResolverBaseDir2(kind, options) {
|
|
2249
|
+
pathsResolverAssertKind2(kind);
|
|
2250
|
+
const env = options.env ?? process.env;
|
|
2251
|
+
const override = env[PATHS_RESOLVER_KIND_ENV2[kind]];
|
|
2252
|
+
if (typeof override === "string" && override.length > 0)
|
|
2187
2253
|
return override;
|
|
2188
|
-
const home = options.home ??
|
|
2254
|
+
const home = options.home ?? pathsResolverHomedir2();
|
|
2189
2255
|
const platform = options.platform ?? process.platform;
|
|
2190
|
-
if (
|
|
2256
|
+
if (platform === "darwin") {
|
|
2191
2257
|
switch (kind) {
|
|
2192
2258
|
case "config":
|
|
2193
2259
|
case "data":
|
|
2194
|
-
return
|
|
2260
|
+
return pathsResolverJoin2(home, "Library", "Application Support", "Hasna");
|
|
2195
2261
|
case "cache":
|
|
2196
|
-
return
|
|
2262
|
+
return pathsResolverJoin2(home, "Library", "Caches", "Hasna");
|
|
2197
2263
|
case "state":
|
|
2198
|
-
return
|
|
2264
|
+
return pathsResolverJoin2(home, "Library", "Logs", "Hasna");
|
|
2199
2265
|
}
|
|
2200
2266
|
}
|
|
2201
2267
|
switch (kind) {
|
|
2202
2268
|
case "config":
|
|
2203
|
-
return
|
|
2269
|
+
return pathsResolverJoin2(home, ".config", "hasna");
|
|
2204
2270
|
case "data":
|
|
2205
|
-
return
|
|
2271
|
+
return pathsResolverJoin2(home, ".local", "share", "hasna");
|
|
2206
2272
|
case "state":
|
|
2207
|
-
return
|
|
2273
|
+
return pathsResolverJoin2(home, ".local", "state", "hasna");
|
|
2208
2274
|
case "cache":
|
|
2209
|
-
return
|
|
2275
|
+
return pathsResolverJoin2(home, ".cache", "hasna");
|
|
2210
2276
|
}
|
|
2211
2277
|
}
|
|
2212
|
-
function
|
|
2213
|
-
|
|
2214
|
-
const appSegment = options.internal === true ?
|
|
2215
|
-
return
|
|
2278
|
+
function pathsResolverResolve2(kind, options) {
|
|
2279
|
+
pathsResolverAssertApp2(options.app);
|
|
2280
|
+
const appSegment = options.internal === true ? pathsResolverJoin2("internal", options.app) : options.app;
|
|
2281
|
+
return pathsResolverJoin2(pathsResolverBaseDir2(kind, options), appSegment);
|
|
2216
2282
|
}
|
|
2217
2283
|
function configDir(options) {
|
|
2218
|
-
return
|
|
2219
|
-
}
|
|
2220
|
-
function stateDir(options) {
|
|
2221
|
-
return resolvePath2("state", options);
|
|
2284
|
+
return pathsResolverResolve2("config", options);
|
|
2222
2285
|
}
|
|
2223
|
-
var KIND_ENV2, APP_SLUG_RE2;
|
|
2224
|
-
var init_dist = __esm(() => {
|
|
2225
|
-
KIND_ENV2 = {
|
|
2226
|
-
config: "HASNA_CONFIG_HOME",
|
|
2227
|
-
data: "HASNA_DATA_HOME",
|
|
2228
|
-
state: "HASNA_STATE_HOME",
|
|
2229
|
-
cache: "HASNA_CACHE_HOME"
|
|
2230
|
-
};
|
|
2231
|
-
APP_SLUG_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
2232
|
-
});
|
|
2233
|
-
|
|
2234
|
-
// src/lib/app-home.ts
|
|
2235
|
-
import { existsSync as existsSync3 } from "fs";
|
|
2236
|
-
import { homedir as homedir5 } from "os";
|
|
2237
|
-
import { join as join6, resolve as resolve2 } from "path";
|
|
2238
2286
|
function homeDir(env = process.env) {
|
|
2239
|
-
return env["HOME"] || env["USERPROFILE"] ||
|
|
2287
|
+
return env["HOME"] || env["USERPROFILE"] || homedir2();
|
|
2240
2288
|
}
|
|
2241
2289
|
function legacyStoreHome(env = process.env) {
|
|
2242
|
-
return resolve2(
|
|
2290
|
+
return resolve2(join3(homeDir(env), ".hasna", "instructions"));
|
|
2243
2291
|
}
|
|
2244
2292
|
function resolverStoreHome(env = process.env) {
|
|
2245
|
-
return configDir({ app: "configs", env, home: env.HOME || env.USERPROFILE ||
|
|
2293
|
+
return configDir({ app: "configs", env, home: env.HOME || env.USERPROFILE || homedir2() });
|
|
2246
2294
|
}
|
|
2247
2295
|
function adoptResolverStoreHome(resolved, env = process.env) {
|
|
2248
2296
|
const override = env.HASNA_CONFIG_HOME;
|
|
2249
2297
|
if (typeof override === "string" && override.trim().length > 0)
|
|
2250
2298
|
return true;
|
|
2251
|
-
return existsSync3(
|
|
2299
|
+
return existsSync3(join3(resolved, "instructions.db"));
|
|
2252
2300
|
}
|
|
2253
2301
|
function exactStoreHome(env = process.env) {
|
|
2254
2302
|
const v = env[HASNA_CONFIGS_HOME_ENV];
|
|
@@ -2261,9 +2309,15 @@ function getConfigsStoreHome(env = process.env) {
|
|
|
2261
2309
|
const resolved = resolverStoreHome(env);
|
|
2262
2310
|
return adoptResolverStoreHome(resolved, env) ? resolve2(resolved) : legacyStoreHome(env);
|
|
2263
2311
|
}
|
|
2264
|
-
var HASNA_CONFIGS_HOME_ENV = "HASNA_CONFIGS_HOME";
|
|
2312
|
+
var PATHS_RESOLVER_KIND_ENV2, PATHS_RESOLVER_APP_SLUG_RE2, HASNA_CONFIGS_HOME_ENV = "HASNA_CONFIGS_HOME";
|
|
2265
2313
|
var init_app_home = __esm(() => {
|
|
2266
|
-
|
|
2314
|
+
PATHS_RESOLVER_KIND_ENV2 = {
|
|
2315
|
+
config: "HASNA_CONFIG_HOME",
|
|
2316
|
+
data: "HASNA_DATA_HOME",
|
|
2317
|
+
state: "HASNA_STATE_HOME",
|
|
2318
|
+
cache: "HASNA_CACHE_HOME"
|
|
2319
|
+
};
|
|
2320
|
+
PATHS_RESOLVER_APP_SLUG_RE2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
2267
2321
|
});
|
|
2268
2322
|
|
|
2269
2323
|
// src/lib/raw-store-root.ts
|
|
@@ -2278,7 +2332,7 @@ var init_raw_store_root = __esm(() => {
|
|
|
2278
2332
|
// src/db/database.ts
|
|
2279
2333
|
import { Database } from "bun:sqlite";
|
|
2280
2334
|
import { existsSync as existsSync5, mkdirSync, rmSync } from "fs";
|
|
2281
|
-
import { join as
|
|
2335
|
+
import { join as join5 } from "path";
|
|
2282
2336
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2283
2337
|
function getDbPath() {
|
|
2284
2338
|
if (process.env["HASNA_INSTRUCTIONS_DB_PATH"]) {
|
|
@@ -2286,7 +2340,7 @@ function getDbPath() {
|
|
|
2286
2340
|
}
|
|
2287
2341
|
const dir = getRawStoreRoot();
|
|
2288
2342
|
mkdirSync(dir, { recursive: true });
|
|
2289
|
-
return
|
|
2343
|
+
return join5(dir, "instructions.db");
|
|
2290
2344
|
}
|
|
2291
2345
|
function uuid() {
|
|
2292
2346
|
return randomUUID3();
|
|
@@ -2300,9 +2354,8 @@ function slugify(name) {
|
|
|
2300
2354
|
function getDatabase(path) {
|
|
2301
2355
|
if (_db)
|
|
2302
2356
|
return _db;
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
throw new Error("instructions is using the HTTP API transport (HASNA_INSTRUCTIONS_API_URL set): this command is not wired to the API yet. " + "Unset HASNA_INSTRUCTIONS_API_URL / HASNA_INSTRUCTIONS_API_KEY to use it against the local store.");
|
|
2357
|
+
if (!path && hasInstructionsEnvAuthorityIntent(process.env)) {
|
|
2358
|
+
throw new Error("instructions is using the hosted API transport (a HASNA_INSTRUCTIONS_* credential is configured): this command is not wired to the API yet. " + "Point this run at the local store (HASNA_INSTRUCTIONS_LOCAL=1 with no hosted credential) to use it against the local SQLite store.");
|
|
2306
2359
|
}
|
|
2307
2360
|
const dbPath = path || getDbPath();
|
|
2308
2361
|
const db = new Database(dbPath);
|
|
@@ -2383,7 +2436,7 @@ function insertFeedback(input, db) {
|
|
|
2383
2436
|
}
|
|
2384
2437
|
var MIGRATIONS, _db = null;
|
|
2385
2438
|
var init_database = __esm(() => {
|
|
2386
|
-
|
|
2439
|
+
init_local_opt_in();
|
|
2387
2440
|
init_raw_store_root();
|
|
2388
2441
|
MIGRATIONS = [
|
|
2389
2442
|
`
|
|
@@ -2764,9 +2817,9 @@ var init_template = __esm(() => {
|
|
|
2764
2817
|
});
|
|
2765
2818
|
|
|
2766
2819
|
// src/lib/machine.ts
|
|
2767
|
-
import { arch as currentArch, homedir as
|
|
2820
|
+
import { arch as currentArch, homedir as homedir3, hostname as currentHostname, type as currentOsType } from "os";
|
|
2768
2821
|
import { existsSync as existsSync6 } from "fs";
|
|
2769
|
-
import { join as
|
|
2822
|
+
import { join as join6 } from "path";
|
|
2770
2823
|
function normalizeOsFamily(os) {
|
|
2771
2824
|
const value = (os ?? "").trim().toLowerCase();
|
|
2772
2825
|
if (value === "darwin" || value === "macos" || value === "mac" || value === "osx")
|
|
@@ -2778,11 +2831,11 @@ function normalizeOsFamily(os) {
|
|
|
2778
2831
|
return value || "unknown";
|
|
2779
2832
|
}
|
|
2780
2833
|
function detectMachineContext(overrides = {}) {
|
|
2781
|
-
const homeDir2 = overrides.home_dir ?? process.env["CONFIGS_HOME"] ?? process.env["HOME"] ??
|
|
2834
|
+
const homeDir2 = overrides.home_dir ?? process.env["CONFIGS_HOME"] ?? process.env["HOME"] ?? homedir3();
|
|
2782
2835
|
const os = overrides.os ?? currentOsType();
|
|
2783
2836
|
const osFamily = normalizeOsFamily(os);
|
|
2784
|
-
const bunBinDir = overrides.bun_bin_dir ??
|
|
2785
|
-
const defaultBunPath = osFamily === "macos" && existsSync6(BREW_BUN_PATH) ? BREW_BUN_PATH :
|
|
2837
|
+
const bunBinDir = overrides.bun_bin_dir ?? join6(homeDir2, ".bun", "bin");
|
|
2838
|
+
const defaultBunPath = osFamily === "macos" && existsSync6(BREW_BUN_PATH) ? BREW_BUN_PATH : join6(bunBinDir, "bun");
|
|
2786
2839
|
return {
|
|
2787
2840
|
id: "current-machine",
|
|
2788
2841
|
hostname: overrides.hostname ?? currentHostname(),
|
|
@@ -2792,10 +2845,10 @@ function detectMachineContext(overrides = {}) {
|
|
|
2792
2845
|
created_at: "",
|
|
2793
2846
|
os_family: osFamily,
|
|
2794
2847
|
home_dir: homeDir2,
|
|
2795
|
-
workspace_root: overrides.workspace_root ??
|
|
2848
|
+
workspace_root: overrides.workspace_root ?? join6(homeDir2, osFamily === "macos" ? "Workspace" : "workspace"),
|
|
2796
2849
|
bun_bin_dir: bunBinDir,
|
|
2797
2850
|
bun_path: overrides.bun_path ?? defaultBunPath,
|
|
2798
|
-
path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${
|
|
2851
|
+
path_prefix: overrides.path_prefix ?? (osFamily === "macos" ? `${join6("/opt", "homebrew", "bin")}:${bunBinDir}` : bunBinDir)
|
|
2799
2852
|
};
|
|
2800
2853
|
}
|
|
2801
2854
|
function machineContextToVariables(machine) {
|
|
@@ -7425,13 +7478,66 @@ var init_session_render_contract = __esm(() => {
|
|
|
7425
7478
|
|
|
7426
7479
|
// src/lib/session-render-state.ts
|
|
7427
7480
|
import { existsSync as existsSync7, readdirSync } from "fs";
|
|
7428
|
-
import { homedir as
|
|
7429
|
-
import { dirname, join as
|
|
7481
|
+
import { homedir as homedir4 } from "os";
|
|
7482
|
+
import { dirname, join as join7, resolve as resolve4 } from "path";
|
|
7483
|
+
import { homedir as pathsResolverHomedir3 } from "os";
|
|
7484
|
+
import { join as pathsResolverJoin3 } from "path";
|
|
7485
|
+
function pathsResolverAssertApp3(app) {
|
|
7486
|
+
if (typeof app !== "string" || app.length === 0) {
|
|
7487
|
+
throw new TypeError("paths: app must be a non-empty string");
|
|
7488
|
+
}
|
|
7489
|
+
if (!PATHS_RESOLVER_APP_SLUG_RE3.test(app)) {
|
|
7490
|
+
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
7491
|
+
}
|
|
7492
|
+
}
|
|
7493
|
+
function pathsResolverAssertKind3(kind) {
|
|
7494
|
+
if (!Object.keys(PATHS_RESOLVER_KIND_ENV3).includes(kind)) {
|
|
7495
|
+
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV3).join(", ")}`);
|
|
7496
|
+
}
|
|
7497
|
+
}
|
|
7498
|
+
function pathsResolverBaseDir3(kind, options) {
|
|
7499
|
+
pathsResolverAssertKind3(kind);
|
|
7500
|
+
const env = options.env ?? process.env;
|
|
7501
|
+
const override = env[PATHS_RESOLVER_KIND_ENV3[kind]];
|
|
7502
|
+
if (typeof override === "string" && override.length > 0)
|
|
7503
|
+
return override;
|
|
7504
|
+
const home = options.home ?? pathsResolverHomedir3();
|
|
7505
|
+
const platform = options.platform ?? process.platform;
|
|
7506
|
+
if (platform === "darwin") {
|
|
7507
|
+
switch (kind) {
|
|
7508
|
+
case "config":
|
|
7509
|
+
case "data":
|
|
7510
|
+
return pathsResolverJoin3(home, "Library", "Application Support", "Hasna");
|
|
7511
|
+
case "cache":
|
|
7512
|
+
return pathsResolverJoin3(home, "Library", "Caches", "Hasna");
|
|
7513
|
+
case "state":
|
|
7514
|
+
return pathsResolverJoin3(home, "Library", "Logs", "Hasna");
|
|
7515
|
+
}
|
|
7516
|
+
}
|
|
7517
|
+
switch (kind) {
|
|
7518
|
+
case "config":
|
|
7519
|
+
return pathsResolverJoin3(home, ".config", "hasna");
|
|
7520
|
+
case "data":
|
|
7521
|
+
return pathsResolverJoin3(home, ".local", "share", "hasna");
|
|
7522
|
+
case "state":
|
|
7523
|
+
return pathsResolverJoin3(home, ".local", "state", "hasna");
|
|
7524
|
+
case "cache":
|
|
7525
|
+
return pathsResolverJoin3(home, ".cache", "hasna");
|
|
7526
|
+
}
|
|
7527
|
+
}
|
|
7528
|
+
function pathsResolverResolve3(kind, options) {
|
|
7529
|
+
pathsResolverAssertApp3(options.app);
|
|
7530
|
+
const appSegment = options.internal === true ? pathsResolverJoin3("internal", options.app) : options.app;
|
|
7531
|
+
return pathsResolverJoin3(pathsResolverBaseDir3(kind, options), appSegment);
|
|
7532
|
+
}
|
|
7533
|
+
function stateDir(options) {
|
|
7534
|
+
return pathsResolverResolve3("state", options);
|
|
7535
|
+
}
|
|
7430
7536
|
function homeDir2(env = process.env) {
|
|
7431
|
-
return env["HOME"] || env["USERPROFILE"] ||
|
|
7537
|
+
return env["HOME"] || env["USERPROFILE"] || homedir4();
|
|
7432
7538
|
}
|
|
7433
7539
|
function legacySnapshotDir(targetHome) {
|
|
7434
|
-
return resolve4(
|
|
7540
|
+
return resolve4(join7(targetHome, ".hasna", "session-render-snapshots"));
|
|
7435
7541
|
}
|
|
7436
7542
|
function resolverSnapshotDir(env = process.env) {
|
|
7437
7543
|
const override = env.HASNA_STATE_HOME;
|
|
@@ -7467,9 +7573,15 @@ function getSessionRenderSnapshotDir(targetHome, env = process.env) {
|
|
|
7467
7573
|
function sessionRenderSnapshotWorkspaceRoot(targetHome, env = process.env) {
|
|
7468
7574
|
return resolveSessionRenderSnapshotLocation(targetHome, env).workspaceRoot;
|
|
7469
7575
|
}
|
|
7470
|
-
var SESSION_RENDER_STATE_APP = "instructions";
|
|
7576
|
+
var PATHS_RESOLVER_KIND_ENV3, PATHS_RESOLVER_APP_SLUG_RE3, SESSION_RENDER_STATE_APP = "instructions";
|
|
7471
7577
|
var init_session_render_state = __esm(() => {
|
|
7472
|
-
|
|
7578
|
+
PATHS_RESOLVER_KIND_ENV3 = {
|
|
7579
|
+
config: "HASNA_CONFIG_HOME",
|
|
7580
|
+
data: "HASNA_DATA_HOME",
|
|
7581
|
+
state: "HASNA_STATE_HOME",
|
|
7582
|
+
cache: "HASNA_CACHE_HOME"
|
|
7583
|
+
};
|
|
7584
|
+
PATHS_RESOLVER_APP_SLUG_RE3 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
7473
7585
|
});
|
|
7474
7586
|
|
|
7475
7587
|
// src/lib/project-context.ts
|
|
@@ -7492,7 +7604,7 @@ import {
|
|
|
7492
7604
|
statSync,
|
|
7493
7605
|
writeFileSync
|
|
7494
7606
|
} from "fs";
|
|
7495
|
-
import { basename, dirname as dirname2, isAbsolute, join as
|
|
7607
|
+
import { basename, dirname as dirname2, isAbsolute, join as join8, parse, relative, resolve as resolve5 } from "path";
|
|
7496
7608
|
function managedObservationMaxBytes(relativePath) {
|
|
7497
7609
|
return SESSION_MANAGED_OUTPUT_PATHS.includes(relativePath) ? SESSION_MANAGED_OUTPUT_MAX_BYTES : FOREIGN_INPUT_MAX_BYTES;
|
|
7498
7610
|
}
|
|
@@ -8734,7 +8846,7 @@ function atomicWriteFile(path, content, workspaceRoot, defaultMode, expectedHash
|
|
|
8734
8846
|
const previous = anchoredFileObservation(directory, targetName);
|
|
8735
8847
|
const previousMode = previous?.mode ?? defaultMode;
|
|
8736
8848
|
const tempName = `.project-context-${randomUUID5()}.tmp`;
|
|
8737
|
-
const tempPath =
|
|
8849
|
+
const tempPath = join8(dir, tempName);
|
|
8738
8850
|
let fd = null;
|
|
8739
8851
|
let preserveTemp = false;
|
|
8740
8852
|
let directoryChanged = false;
|
|
@@ -8865,7 +8977,7 @@ function atomicWritePortable(path, content, workspaceRoot, defaultMode, expected
|
|
|
8865
8977
|
}
|
|
8866
8978
|
const dir = dirname2(path);
|
|
8867
8979
|
const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
|
|
8868
|
-
const tempPath =
|
|
8980
|
+
const tempPath = join8(dir, `.project-context-${randomUUID5()}.tmp`);
|
|
8869
8981
|
let fd = null;
|
|
8870
8982
|
let tempIdentity = null;
|
|
8871
8983
|
try {
|
|
@@ -8922,7 +9034,7 @@ function atomicWritePortableReplacement(path, content, workspaceRoot, expectedHa
|
|
|
8922
9034
|
}
|
|
8923
9035
|
const dir = dirname2(path);
|
|
8924
9036
|
const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
|
|
8925
|
-
const tempPath =
|
|
9037
|
+
const tempPath = join8(dir, `.project-context-${randomUUID5()}.tmp`);
|
|
8926
9038
|
const desiredHash = sha2562(content);
|
|
8927
9039
|
let fd = null;
|
|
8928
9040
|
let tempIdentity = null;
|
|
@@ -9021,7 +9133,7 @@ function removeProjectContextCoordinatedFile(input) {
|
|
|
9021
9133
|
throw new ProjectContextHashRace(`managed path changed during deletion: ${relativePosix(workspaceRoot, path)}`);
|
|
9022
9134
|
}
|
|
9023
9135
|
displaced = true;
|
|
9024
|
-
input.test_hooks?.after_displace?.(
|
|
9136
|
+
input.test_hooks?.after_displace?.(join8(dir, displacedName));
|
|
9025
9137
|
const moved = anchoredFileObservation(directory, displacedName);
|
|
9026
9138
|
if (!moved || moved.dev !== observed.dev || moved.ino !== observed.ino || moved.hash !== input.expected_hash || anchoredFileObservation(directory, targetName) !== null) {
|
|
9027
9139
|
throw new ProjectContextHashRace(`managed path changed during deletion validation: ${relativePosix(workspaceRoot, path)}`);
|
|
@@ -9067,7 +9179,7 @@ function removePortableCoordinatedFile(path, workspaceRoot, expectedHash, maxObs
|
|
|
9067
9179
|
}
|
|
9068
9180
|
const dir = dirname2(path);
|
|
9069
9181
|
const directoryIdentity = captureManagedDirectoryIdentity(dir, workspaceRoot);
|
|
9070
|
-
const displacedPath =
|
|
9182
|
+
const displacedPath = join8(dir, `.project-context-delete-${randomUUID5()}.tmp`);
|
|
9071
9183
|
let displaced = false;
|
|
9072
9184
|
try {
|
|
9073
9185
|
assertManagedDirectoryStable(dir, workspaceRoot, directoryIdentity);
|
|
@@ -9138,7 +9250,7 @@ function anchoredOpenExclusive(directory, name, mode) {
|
|
|
9138
9250
|
const requestedMode = mode & 4095;
|
|
9139
9251
|
let fd;
|
|
9140
9252
|
try {
|
|
9141
|
-
fd = openSync(
|
|
9253
|
+
fd = openSync(join8(directory.path, name), constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, requestedMode);
|
|
9142
9254
|
} catch {
|
|
9143
9255
|
throw new ProjectContextHashRace(`could not create prepared managed file in ${relativePosix(directory.workspaceRoot, directory.path)}`);
|
|
9144
9256
|
}
|
|
@@ -9181,7 +9293,7 @@ function anchoredFileObservation(directory, name) {
|
|
|
9181
9293
|
const stat = fstatSync(fd);
|
|
9182
9294
|
if (!stat.isFile())
|
|
9183
9295
|
throw new ProjectContextHashRace("managed output is not a regular file");
|
|
9184
|
-
const relativePath = relativePosix(directory.workspaceRoot,
|
|
9296
|
+
const relativePath = relativePosix(directory.workspaceRoot, join8(directory.path, name));
|
|
9185
9297
|
const maxBytes = directory.maxObservedBytes === undefined ? managedObservationMaxBytes(relativePath) : directory.maxObservedBytes;
|
|
9186
9298
|
if (maxBytes !== null && stat.size > maxBytes) {
|
|
9187
9299
|
throw new ProjectContextHashRace(`managed output exceeds the safe read limit: ${relativePath}`);
|
|
@@ -9207,7 +9319,7 @@ function anchoredPreparedObservation(directory, name, path, stage) {
|
|
|
9207
9319
|
return observed;
|
|
9208
9320
|
}
|
|
9209
9321
|
function captureManagedDirectoryIdentity(path, workspaceRoot) {
|
|
9210
|
-
assertNoSymlinkSegments(workspaceRoot,
|
|
9322
|
+
assertNoSymlinkSegments(workspaceRoot, join8(path, ".project-context-directory-guard"));
|
|
9211
9323
|
let stat;
|
|
9212
9324
|
try {
|
|
9213
9325
|
stat = lstatSync(path);
|
|
@@ -9220,7 +9332,7 @@ function captureManagedDirectoryIdentity(path, workspaceRoot) {
|
|
|
9220
9332
|
return { dev: stat.dev, ino: stat.ino };
|
|
9221
9333
|
}
|
|
9222
9334
|
function assertManagedDirectoryStable(path, workspaceRoot, expected) {
|
|
9223
|
-
assertNoSymlinkSegments(workspaceRoot,
|
|
9335
|
+
assertNoSymlinkSegments(workspaceRoot, join8(path, ".project-context-directory-guard"));
|
|
9224
9336
|
let current;
|
|
9225
9337
|
try {
|
|
9226
9338
|
current = lstatSync(path);
|
|
@@ -9356,7 +9468,7 @@ function acquireWorkspaceLock(workspaceRoot, lockPath, afterOpen, beforeStaleRem
|
|
|
9356
9468
|
const lockDirectory = resolve5(lockPath, "..");
|
|
9357
9469
|
ensureSafeDirectory(lockDirectory, workspaceRoot, 448);
|
|
9358
9470
|
assertNoSymlinkSegments(workspaceRoot, lockPath);
|
|
9359
|
-
const tempPath =
|
|
9471
|
+
const tempPath = join8(lockDirectory, `.project-context-lock-${randomUUID5()}.tmp`);
|
|
9360
9472
|
let fd = null;
|
|
9361
9473
|
let openedIdentity = null;
|
|
9362
9474
|
let openedContentHash = null;
|
|
@@ -9587,7 +9699,7 @@ function releaseWorkspaceLock(lockPath, lock, workspaceRoot) {
|
|
|
9587
9699
|
return;
|
|
9588
9700
|
}
|
|
9589
9701
|
const lockDirectory = resolve5(lockPath, "..");
|
|
9590
|
-
const releasePath =
|
|
9702
|
+
const releasePath = join8(lockDirectory, `.project-context-release-${randomUUID5()}.tmp`);
|
|
9591
9703
|
let releaseFd = null;
|
|
9592
9704
|
let releaseIdentity = null;
|
|
9593
9705
|
let releaseHash = null;
|
|
@@ -9667,7 +9779,7 @@ function ensureSafeDirectory(path, workspaceRoot, mode) {
|
|
|
9667
9779
|
const segments = rel.split(/[\\/]+/).filter(Boolean);
|
|
9668
9780
|
let current = workspaceRoot;
|
|
9669
9781
|
for (const segment of segments) {
|
|
9670
|
-
current =
|
|
9782
|
+
current = join8(current, segment);
|
|
9671
9783
|
if (existsSync8(current)) {
|
|
9672
9784
|
if (lstatSync(current).isSymbolicLink())
|
|
9673
9785
|
throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `managed path uses a symlink: ${current}`);
|
|
@@ -9812,7 +9924,7 @@ function assertNoSymlinkSegments(root, target) {
|
|
|
9812
9924
|
}
|
|
9813
9925
|
let current = root;
|
|
9814
9926
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
9815
|
-
current =
|
|
9927
|
+
current = join8(current, segment);
|
|
9816
9928
|
if (existsSync8(current) && lstatSync(current).isSymbolicLink()) {
|
|
9817
9929
|
throw new ProjectContextError("PROJECT_CONTEXT_SYMLINK_REJECTED", `managed path uses a symlink: ${current}`);
|
|
9818
9930
|
}
|
|
@@ -9822,7 +9934,7 @@ function assertNoSymlinkAncestors(path) {
|
|
|
9822
9934
|
const normalized = resolve5(path);
|
|
9823
9935
|
let current = parse(normalized).root;
|
|
9824
9936
|
for (const segment of relative(current, normalized).split(/[\\/]+/).filter(Boolean)) {
|
|
9825
|
-
current =
|
|
9937
|
+
current = join8(current, segment);
|
|
9826
9938
|
if (!existsSync8(current))
|
|
9827
9939
|
return;
|
|
9828
9940
|
if (lstatSync(current).isSymbolicLink())
|
|
@@ -10028,7 +10140,7 @@ function sha2562(content) {
|
|
|
10028
10140
|
function isRecord(value) {
|
|
10029
10141
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
10030
10142
|
}
|
|
10031
|
-
var PROJECT_CONTEXT_SCHEMA = "hasna.projects.project_context_bundle.v1", PROJECT_CONTEXT_SCHEMA_V2 = "hasna.projects.project_context_bundle.v2", PROJECT_CONTEXT_SUPPORTED_SCHEMAS, projectContextSchema, PROJECT_CONTEXT_MAX_INPUT_BYTES, PROJECT_CONTEXT_MAX_RENDERED_BYTES, PROJECT_CONTEXT_MAX_APPROX_TOKENS = 1000, PROJECT_CONTEXT_MAX_COMMANDS = 6, PROJECT_CONTEXT_MAX_WARNINGS = 3, PROJECT_CONTEXT_FRAGMENT_PATH = ".hasna/instructions/project-context.md", PROJECT_CONTEXT_MANIFEST_PATH = ".hasna/project-context-manifest.json", PROJECT_CONTEXT_CACHE_PATH = ".hasna/project-context-cache.json", PROJECT_CONTEXT_LOCK_PATH = ".hasna/project-context.lock", PROJECT_CONTEXT_SNAPSHOT_DIR = ".hasna/project-context-snapshots", PROJECT_CONTEXT_CACHE_SCHEMA = "hasna.instructions.project-context-cache/v1", PROJECT_CONTEXT_MANAGED_COMMENT = "Managed by @hasna/configs project context", SESSION_COMPATIBILITY_MANIFEST_MAX_BYTES, FOREIGN_INPUT_MAX_BYTES, SESSION_MANAGED_OUTPUT_MAX_BYTES, SESSION_MANAGED_INPUT_MAX_BYTES, SESSION_MANAGED_OUTPUT_PATHS, SESSION_MANAGED_OUTPUT_WARN_BYTES, PROJECT_CONTEXT_LOCK_STALE_MS, LEGACY_CONFIGS_PACKAGE = "@hasna/configs", LEGACY_CONFIGS_COMPAT_VERSION = "0.2.45", LEGACY_CONFIGS_EXECUTABLE = "configs", PROJECT_KINDS, PROJECT_STATUSES, LINK_STATES, RESOLUTION_SOURCES, safeId, nullableId, producerSlug, producerName, safeOptionalDisplay, isoTimestamp, revisionSchema, hashSchema, absolutePath, commandArg, financeText, financeLegalEntity, financeProjectMetadataSchema, commandSchema, projectContextBundleSchema, storedManifestProjectContextSchema, storedManifestFileSchema, storedManifestObservationSchema, projectContextMetadataSnapshotSchema, projectContextCacheSchema, ProjectContextError, ProjectContextHashRace, anchoredFsOps, atomicExchange, atomicExchangeLibraries;
|
|
10143
|
+
var PROJECT_CONTEXT_SCHEMA = "hasna.projects.project_context_bundle.v1", PROJECT_CONTEXT_SCHEMA_V2 = "hasna.projects.project_context_bundle.v2", PROJECT_CONTEXT_SUPPORTED_SCHEMAS, projectContextSchema, PROJECT_CONTEXT_MAX_INPUT_BYTES, PROJECT_CONTEXT_MAX_RENDERED_BYTES, PROJECT_CONTEXT_MAX_APPROX_TOKENS = 1000, PROJECT_CONTEXT_MAX_COMMANDS = 6, PROJECT_CONTEXT_MAX_WARNINGS = 3, PROJECT_CONTEXT_FRAGMENT_PATH = ".hasna/instructions/project-context.md", PROJECT_CONTEXT_MANIFEST_PATH = ".hasna/projects/project-context-manifest.json", PROJECT_CONTEXT_CACHE_PATH = ".hasna/projects/project-context-cache.json", PROJECT_CONTEXT_LOCK_PATH = ".hasna/projects/project-context.lock", PROJECT_CONTEXT_SNAPSHOT_DIR = ".hasna/projects/project-context-snapshots", PROJECT_CONTEXT_CACHE_SCHEMA = "hasna.instructions.project-context-cache/v1", PROJECT_CONTEXT_MANAGED_COMMENT = "Managed by @hasna/configs project context", SESSION_COMPATIBILITY_MANIFEST_MAX_BYTES, FOREIGN_INPUT_MAX_BYTES, SESSION_MANAGED_OUTPUT_MAX_BYTES, SESSION_MANAGED_INPUT_MAX_BYTES, SESSION_MANAGED_OUTPUT_PATHS, SESSION_MANAGED_OUTPUT_WARN_BYTES, PROJECT_CONTEXT_LOCK_STALE_MS, LEGACY_CONFIGS_PACKAGE = "@hasna/configs", LEGACY_CONFIGS_COMPAT_VERSION = "0.2.45", LEGACY_CONFIGS_EXECUTABLE = "configs", PROJECT_KINDS, PROJECT_STATUSES, LINK_STATES, RESOLUTION_SOURCES, safeId, nullableId, producerSlug, producerName, safeOptionalDisplay, isoTimestamp, revisionSchema, hashSchema, absolutePath, commandArg, financeText, financeLegalEntity, financeProjectMetadataSchema, commandSchema, projectContextBundleSchema, storedManifestProjectContextSchema, storedManifestFileSchema, storedManifestObservationSchema, projectContextMetadataSnapshotSchema, projectContextCacheSchema, ProjectContextError, ProjectContextHashRace, anchoredFsOps, atomicExchange, atomicExchangeLibraries;
|
|
10032
10144
|
var init_project_context = __esm(() => {
|
|
10033
10145
|
init_zod();
|
|
10034
10146
|
init_redact();
|
|
@@ -10810,13 +10922,13 @@ var init_asset_plan = __esm(() => {
|
|
|
10810
10922
|
// src/lib/cursor-authority.ts
|
|
10811
10923
|
import { createHash as createHash4 } from "crypto";
|
|
10812
10924
|
import { lstatSync as lstatSync2, readFileSync as readFileSync2 } from "fs";
|
|
10813
|
-
import { homedir as
|
|
10814
|
-
import { join as
|
|
10925
|
+
import { homedir as homedir5 } from "os";
|
|
10926
|
+
import { join as join9, resolve as resolve7 } from "path";
|
|
10815
10927
|
function sha2564(content) {
|
|
10816
10928
|
return createHash4("sha256").update(content).digest("hex");
|
|
10817
10929
|
}
|
|
10818
10930
|
function homeDir3() {
|
|
10819
|
-
return process.env["HOME"] ||
|
|
10931
|
+
return process.env["HOME"] || homedir5();
|
|
10820
10932
|
}
|
|
10821
10933
|
function markerPayload(content, markerLine, markerIndex) {
|
|
10822
10934
|
const index = markerIndex ?? content.indexOf(markerLine);
|
|
@@ -10832,7 +10944,7 @@ function baseObservation(path) {
|
|
|
10832
10944
|
};
|
|
10833
10945
|
}
|
|
10834
10946
|
function observeCursorGlobalAuthority(options = {}) {
|
|
10835
|
-
const authorityPath = resolve7(
|
|
10947
|
+
const authorityPath = resolve7(join9(options.home ?? homeDir3(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
|
|
10836
10948
|
const readFile2 = options.readFile ?? ((path) => readFileSync2(path, "utf8"));
|
|
10837
10949
|
return observeCursorGlobalAuthorityPath(authorityPath, readFile2);
|
|
10838
10950
|
}
|
|
@@ -10982,7 +11094,7 @@ function observeCursorGlobalAuthorityPath(authorityPath, readFile2) {
|
|
|
10982
11094
|
};
|
|
10983
11095
|
}
|
|
10984
11096
|
function isCursorGlobalAuthorityPath(path) {
|
|
10985
|
-
return resolve7(path) === resolve7(
|
|
11097
|
+
return resolve7(path) === resolve7(join9(homeDir3(), CURSOR_GLOBAL_AUTHORITY_RELATIVE_PATH));
|
|
10986
11098
|
}
|
|
10987
11099
|
function stampCursorGlobalAuthorityMarker(content) {
|
|
10988
11100
|
const existing = content.match(CURSOR_GLOBAL_AUTHORITY_MARKER_PATTERN);
|
|
@@ -11038,13 +11150,13 @@ var init_cursor_authority = __esm(() => {
|
|
|
11038
11150
|
// src/lib/session-authority.ts
|
|
11039
11151
|
import { createHash as createHash5 } from "crypto";
|
|
11040
11152
|
import { lstatSync as lstatSync3, readFileSync as readFileSync3, realpathSync, statSync as statSync2 } from "fs";
|
|
11041
|
-
import { homedir as
|
|
11042
|
-
import { join as
|
|
11153
|
+
import { homedir as homedir6 } from "os";
|
|
11154
|
+
import { join as join10, resolve as resolve8 } from "path";
|
|
11043
11155
|
function sha2565(content) {
|
|
11044
11156
|
return createHash5("sha256").update(content).digest("hex");
|
|
11045
11157
|
}
|
|
11046
11158
|
function configHomeDir() {
|
|
11047
|
-
return process.env["CONFIGS_HOME"] || process.env["HOME"] ||
|
|
11159
|
+
return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir6();
|
|
11048
11160
|
}
|
|
11049
11161
|
function normalizeOwnedTargetPath(p) {
|
|
11050
11162
|
const expanded = p.startsWith("~/") ? resolve8(configHomeDir(), p.slice(2)) : resolve8(p);
|
|
@@ -11055,7 +11167,7 @@ function normalizeOwnedTargetPath(p) {
|
|
|
11055
11167
|
}
|
|
11056
11168
|
}
|
|
11057
11169
|
function detectClaudeAuthorityConflicts(targetHome, ownedAuthorities = []) {
|
|
11058
|
-
const authorityPath = resolve8(
|
|
11170
|
+
const authorityPath = resolve8(join10(targetHome, CLAUDE_LEGACY_AUTHORITY_RELATIVE_PATH));
|
|
11059
11171
|
let stat;
|
|
11060
11172
|
try {
|
|
11061
11173
|
stat = lstatSync3(authorityPath);
|
|
@@ -11145,8 +11257,8 @@ var init_session_authority = __esm(() => {
|
|
|
11145
11257
|
// src/lib/session-render.ts
|
|
11146
11258
|
import { createHash as createHash6 } from "crypto";
|
|
11147
11259
|
import { existsSync as existsSync10, readFileSync as readFileSync4, realpathSync as realpathSync2, statSync as statSync3 } from "fs";
|
|
11148
|
-
import { homedir as
|
|
11149
|
-
import { basename as basename4, dirname as dirname4, extname as extname2, isAbsolute as isAbsolute3, join as
|
|
11260
|
+
import { homedir as homedir7 } from "os";
|
|
11261
|
+
import { basename as basename4, dirname as dirname4, extname as extname2, isAbsolute as isAbsolute3, join as join11, parse as parse2, posix as posix2, relative as relative2, resolve as resolve9 } from "path";
|
|
11150
11262
|
function normalizeSessionInstructionLayer(value) {
|
|
11151
11263
|
if (value === "provider")
|
|
11152
11264
|
return "tool";
|
|
@@ -11232,13 +11344,13 @@ function yamlQuote2(value) {
|
|
|
11232
11344
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
11233
11345
|
}
|
|
11234
11346
|
function defaultTargetHome(tool, profile, sessionId) {
|
|
11235
|
-
const home = process.env["HOME"] ||
|
|
11236
|
-
return
|
|
11347
|
+
const home = process.env["HOME"] || homedir7();
|
|
11348
|
+
return join11(home, ".hasna", "accounts", "profiles", tool, slug(profile));
|
|
11237
11349
|
}
|
|
11238
11350
|
function joinTarget(targetHome, relativePath) {
|
|
11239
11351
|
const safeTargetHome = assertSafeTargetRoot(targetHome);
|
|
11240
11352
|
const safeRelativePath2 = assertSafeRelativePath(relativePath);
|
|
11241
|
-
return
|
|
11353
|
+
return join11(safeTargetHome, ...safeRelativePath2.split("/"));
|
|
11242
11354
|
}
|
|
11243
11355
|
function makeFile(targetHome, relativePath, role, content, sourceIds) {
|
|
11244
11356
|
const safeTargetHome = assertSafeTargetRoot(targetHome);
|
|
@@ -11996,7 +12108,7 @@ function adapterFor(input) {
|
|
|
11996
12108
|
return gatedNativeImports ? CODEWITH_NATIVE_ADAPTER : CODEWITH_FLATTENED_ADAPTER;
|
|
11997
12109
|
}
|
|
11998
12110
|
function getHomeDir() {
|
|
11999
|
-
return process.env["CONFIGS_HOME"] || process.env["HOME"] ||
|
|
12111
|
+
return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir7();
|
|
12000
12112
|
}
|
|
12001
12113
|
function cleanSessionPathInput(path) {
|
|
12002
12114
|
const trimmed = path.trim();
|
|
@@ -12533,10 +12645,10 @@ function layerFromIdentityKind(kind, exportShape) {
|
|
|
12533
12645
|
function contentFromIdentitySourcePaths(sourcePaths, exportPath, sourceId) {
|
|
12534
12646
|
if (sourcePaths.length === 0 || !exportPath)
|
|
12535
12647
|
return;
|
|
12536
|
-
const
|
|
12648
|
+
const baseDir = dirname4(resolveSessionPath(exportPath));
|
|
12537
12649
|
const contents = [];
|
|
12538
12650
|
for (const sourcePath of sourcePaths) {
|
|
12539
|
-
const content = readIdentitySourcePath(sourcePath,
|
|
12651
|
+
const content = readIdentitySourcePath(sourcePath, baseDir, sourceId);
|
|
12540
12652
|
if (content !== undefined)
|
|
12541
12653
|
contents.push({ path: sourcePath.path, content });
|
|
12542
12654
|
}
|
|
@@ -12549,8 +12661,8 @@ ${item.content.trimEnd()}`).join(`
|
|
|
12549
12661
|
|
|
12550
12662
|
`));
|
|
12551
12663
|
}
|
|
12552
|
-
function readIdentitySourcePath(sourcePath,
|
|
12553
|
-
const resolvedPath = resolveIdentitySourcePath(sourcePath.path,
|
|
12664
|
+
function readIdentitySourcePath(sourcePath, baseDir, sourceId) {
|
|
12665
|
+
const resolvedPath = resolveIdentitySourcePath(sourcePath.path, baseDir, sourceId);
|
|
12554
12666
|
if (!existsSync10(resolvedPath)) {
|
|
12555
12667
|
if (sourcePath.required) {
|
|
12556
12668
|
throw new Error(`Required identity instruction source path not found for ${sourceId}: ${sourcePath.path}`);
|
|
@@ -12561,27 +12673,27 @@ function readIdentitySourcePath(sourcePath, baseDir3, sourceId) {
|
|
|
12561
12673
|
if (!stat.isFile()) {
|
|
12562
12674
|
throw new Error(`Identity instruction source path is not a file for ${sourceId}: ${sourcePath.path}`);
|
|
12563
12675
|
}
|
|
12564
|
-
const realBase = realpathSync2(
|
|
12676
|
+
const realBase = realpathSync2(baseDir);
|
|
12565
12677
|
const realPath = realpathSync2(resolvedPath);
|
|
12566
12678
|
if (!pathIsInside(realPath, realBase)) {
|
|
12567
12679
|
throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${sourcePath.path}`);
|
|
12568
12680
|
}
|
|
12569
12681
|
return readFileSync4(realPath, "utf-8");
|
|
12570
12682
|
}
|
|
12571
|
-
function resolveIdentitySourcePath(path,
|
|
12683
|
+
function resolveIdentitySourcePath(path, baseDir, sourceId) {
|
|
12572
12684
|
const cleaned = cleanSessionPathInput(path);
|
|
12573
12685
|
if (!cleaned)
|
|
12574
12686
|
throw new Error(`Identity instruction source path cannot be empty for ${sourceId}.`);
|
|
12575
12687
|
if (cleaned.includes("\\"))
|
|
12576
12688
|
throw new Error(`Identity instruction source path must use POSIX separators for ${sourceId}: ${path}`);
|
|
12577
|
-
const resolvedPath = isAbsolute3(cleaned) ? resolve9(cleaned) : resolve9(
|
|
12578
|
-
if (!pathIsInside(resolvedPath, resolve9(
|
|
12689
|
+
const resolvedPath = isAbsolute3(cleaned) ? resolve9(cleaned) : resolve9(baseDir, cleaned);
|
|
12690
|
+
if (!pathIsInside(resolvedPath, resolve9(baseDir))) {
|
|
12579
12691
|
throw new Error(`Identity instruction source path escapes export directory for ${sourceId}: ${path}`);
|
|
12580
12692
|
}
|
|
12581
12693
|
return resolvedPath;
|
|
12582
12694
|
}
|
|
12583
|
-
function pathIsInside(path,
|
|
12584
|
-
const rel = relative2(
|
|
12695
|
+
function pathIsInside(path, baseDir) {
|
|
12696
|
+
const rel = relative2(baseDir, path);
|
|
12585
12697
|
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
|
|
12586
12698
|
}
|
|
12587
12699
|
function providerTargetsTool(targets, tool) {
|
|
@@ -13682,6 +13794,1222 @@ var init_machines = __esm(() => {
|
|
|
13682
13794
|
init_database();
|
|
13683
13795
|
});
|
|
13684
13796
|
|
|
13797
|
+
// ../contracts/dist/client/storage.js
|
|
13798
|
+
import { isIP as isIP2 } from "net";
|
|
13799
|
+
import { spawnSync } from "child_process";
|
|
13800
|
+
import { closeSync as closeSync2, fstatSync as fstatSync2, openSync as openSync2, readFileSync as readFileSync5 } from "fs";
|
|
13801
|
+
import { O_NOFOLLOW, O_NONBLOCK, O_RDONLY } from "constants";
|
|
13802
|
+
import { createRequire as createRequire2 } from "module";
|
|
13803
|
+
import { hostname as osHostname } from "os";
|
|
13804
|
+
import { isAbsolute as isAbsolute4, join as join12 } from "path";
|
|
13805
|
+
function envToken2(name) {
|
|
13806
|
+
return name.toUpperCase().replace(/-/g, "_");
|
|
13807
|
+
}
|
|
13808
|
+
function clientTransportEnvKeys2(name) {
|
|
13809
|
+
const envSegment = envToken2(name);
|
|
13810
|
+
return {
|
|
13811
|
+
apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
|
|
13812
|
+
apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
|
|
13813
|
+
};
|
|
13814
|
+
}
|
|
13815
|
+
function credentialOverrideEnvKey2(name) {
|
|
13816
|
+
return `HASNA_${envToken2(name)}_API_KEY_OVERRIDE`;
|
|
13817
|
+
}
|
|
13818
|
+
function credentialPointerEnvKey2(name) {
|
|
13819
|
+
return `HASNA_${envToken2(name)}_API_KEY_REF`;
|
|
13820
|
+
}
|
|
13821
|
+
function homeDir4(env) {
|
|
13822
|
+
const home = env.HOME?.trim();
|
|
13823
|
+
return home ? home : null;
|
|
13824
|
+
}
|
|
13825
|
+
function absoluteOverride(env, key) {
|
|
13826
|
+
const value = env[key]?.trim();
|
|
13827
|
+
return value && isAbsolute4(value) ? value : null;
|
|
13828
|
+
}
|
|
13829
|
+
function hasnaHomeDir(env) {
|
|
13830
|
+
const override = absoluteOverride(env, HASNA_HOME_ENV_KEY);
|
|
13831
|
+
if (override)
|
|
13832
|
+
return override;
|
|
13833
|
+
const home = homeDir4(env);
|
|
13834
|
+
return home ? join12(home, HASNA_HOME_DIR) : null;
|
|
13835
|
+
}
|
|
13836
|
+
function appConfigDir(name, env) {
|
|
13837
|
+
const configRoot = absoluteOverride(env, HASNA_CONFIG_HOME_ENV_KEY);
|
|
13838
|
+
if (configRoot)
|
|
13839
|
+
return join12(configRoot, name);
|
|
13840
|
+
const root = hasnaHomeDir(env);
|
|
13841
|
+
return root ? join12(root, name, CONFIG_SUBDIR) : null;
|
|
13842
|
+
}
|
|
13843
|
+
function credentialDiskSourceList(name, env, profile = null) {
|
|
13844
|
+
if (!SAFE_APP_SLUG.test(name))
|
|
13845
|
+
return [];
|
|
13846
|
+
const directory = appConfigDir(name, env);
|
|
13847
|
+
if (!directory)
|
|
13848
|
+
return [];
|
|
13849
|
+
const file = profile ? `${CREDENTIALS_FILE}-${profile}` : CREDENTIALS_FILE;
|
|
13850
|
+
return [{ path: join12(directory, file), tier: "disk" }];
|
|
13851
|
+
}
|
|
13852
|
+
function credentialDiskSources(name, env) {
|
|
13853
|
+
return credentialDiskSourceList(name, env, null).map((s) => s.path);
|
|
13854
|
+
}
|
|
13855
|
+
function profileDiskSources(name, env, profile) {
|
|
13856
|
+
return credentialDiskSourceList(name, env, profile).map((s) => s.path);
|
|
13857
|
+
}
|
|
13858
|
+
function parseEnvFile(text) {
|
|
13859
|
+
const values = new Map;
|
|
13860
|
+
const unusable = new Set;
|
|
13861
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
13862
|
+
const line = rawLine.trim();
|
|
13863
|
+
if (line.length === 0 || line.startsWith("#"))
|
|
13864
|
+
continue;
|
|
13865
|
+
const withoutExport = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
|
|
13866
|
+
const equals = withoutExport.indexOf("=");
|
|
13867
|
+
if (equals <= 0)
|
|
13868
|
+
continue;
|
|
13869
|
+
const key = withoutExport.slice(0, equals).trim();
|
|
13870
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
13871
|
+
continue;
|
|
13872
|
+
let value = withoutExport.slice(equals + 1).trim();
|
|
13873
|
+
const quote = value[0];
|
|
13874
|
+
if (quote === '"' || quote === "'") {
|
|
13875
|
+
if (value.length < 2 || !value.endsWith(quote)) {
|
|
13876
|
+
unusable.add(key);
|
|
13877
|
+
continue;
|
|
13878
|
+
}
|
|
13879
|
+
value = value.slice(1, -1);
|
|
13880
|
+
}
|
|
13881
|
+
if (value.trim().length === 0) {
|
|
13882
|
+
unusable.add(key);
|
|
13883
|
+
continue;
|
|
13884
|
+
}
|
|
13885
|
+
if (values.has(key) && values.get(key) !== value)
|
|
13886
|
+
unusable.add(key);
|
|
13887
|
+
values.set(key, value);
|
|
13888
|
+
}
|
|
13889
|
+
return { values, unusable };
|
|
13890
|
+
}
|
|
13891
|
+
function configFileModeAllowed(mode) {
|
|
13892
|
+
const permissions = mode & 4095;
|
|
13893
|
+
return permissions === 256 || permissions === 384;
|
|
13894
|
+
}
|
|
13895
|
+
function configFileReadsCoherent(before, after) {
|
|
13896
|
+
return before.dev === after.dev && before.ino === after.ino && before.size === after.size && before.mtimeMs === after.mtimeMs && before.ctimeMs === after.ctimeMs;
|
|
13897
|
+
}
|
|
13898
|
+
function readAppConfigFile(path) {
|
|
13899
|
+
const unsafe = (reason) => {
|
|
13900
|
+
throw new CredentialFileUnsafeError(path, reason);
|
|
13901
|
+
};
|
|
13902
|
+
let fd = -1;
|
|
13903
|
+
try {
|
|
13904
|
+
fd = openSync2(path, O_RDONLY | O_NOFOLLOW | O_NONBLOCK);
|
|
13905
|
+
} catch (error) {
|
|
13906
|
+
const code = error.code;
|
|
13907
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
13908
|
+
return null;
|
|
13909
|
+
if (code === "ELOOP")
|
|
13910
|
+
unsafe("the path is a symlink");
|
|
13911
|
+
unsafe(`the path could not be opened (${code ?? "unknown error"})`);
|
|
13912
|
+
}
|
|
13913
|
+
try {
|
|
13914
|
+
const before = fstatSync2(fd);
|
|
13915
|
+
if (!before.isFile())
|
|
13916
|
+
unsafe("the path is not a regular file");
|
|
13917
|
+
if (!configFileModeAllowed(before.mode)) {
|
|
13918
|
+
unsafe(`permission mode ${(before.mode & 4095).toString(8).padStart(4, "0")} is not owner-only 0400 or 0600`);
|
|
13919
|
+
}
|
|
13920
|
+
const uid = process.getuid?.() ?? process.geteuid?.();
|
|
13921
|
+
if (uid !== undefined && before.uid !== uid)
|
|
13922
|
+
unsafe("the file is not owned by the current user");
|
|
13923
|
+
if (before.size > MAX_CREDENTIAL_FILE_BYTES2)
|
|
13924
|
+
unsafe("the file exceeds the size limit");
|
|
13925
|
+
const bytes = readFileSync5(fd);
|
|
13926
|
+
const after = fstatSync2(fd);
|
|
13927
|
+
if (!configFileReadsCoherent(before, after)) {
|
|
13928
|
+
unsafe("the file changed while being read");
|
|
13929
|
+
}
|
|
13930
|
+
return parseEnvFile(bytes.toString("utf8"));
|
|
13931
|
+
} finally {
|
|
13932
|
+
if (fd !== -1)
|
|
13933
|
+
closeSync2(fd);
|
|
13934
|
+
}
|
|
13935
|
+
}
|
|
13936
|
+
function readCredentialFile(path, apiKeyKeys) {
|
|
13937
|
+
const parsed = readAppConfigFile(path);
|
|
13938
|
+
if (!parsed)
|
|
13939
|
+
return null;
|
|
13940
|
+
for (const key of apiKeyKeys) {
|
|
13941
|
+
if (parsed.unusable.has(key)) {
|
|
13942
|
+
throw new CredentialFileUnsafeError(path, `${key} is declared but blank or malformed`);
|
|
13943
|
+
}
|
|
13944
|
+
}
|
|
13945
|
+
const values = apiKeyKeys.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
|
|
13946
|
+
if (new Set(values).size > 1) {
|
|
13947
|
+
throw new CredentialFileUnsafeError(path, "credential aliases disagree");
|
|
13948
|
+
}
|
|
13949
|
+
return values[0] ?? null;
|
|
13950
|
+
}
|
|
13951
|
+
function appConfigDiskValue(name, env, keys) {
|
|
13952
|
+
const wanted = keys.filter((key) => !CREDENTIAL_SHAPED_KEY.test(key));
|
|
13953
|
+
if (wanted.length === 0)
|
|
13954
|
+
return null;
|
|
13955
|
+
for (const path of credentialDiskSources(name, env)) {
|
|
13956
|
+
const parsed = readAppConfigFile(path);
|
|
13957
|
+
if (!parsed)
|
|
13958
|
+
continue;
|
|
13959
|
+
if (wanted.some((key) => parsed.unusable.has(key))) {
|
|
13960
|
+
return { key: wanted.find((key) => parsed.unusable.has(key)), value: "", path, unusable: true };
|
|
13961
|
+
}
|
|
13962
|
+
const values = wanted.map((key) => parsed.values.get(key)?.trim()).filter((value) => Boolean(value));
|
|
13963
|
+
if (new Set(values).size > 1)
|
|
13964
|
+
throw new CredentialFileUnsafeError(path, "configuration aliases disagree");
|
|
13965
|
+
for (const key of wanted) {
|
|
13966
|
+
if (parsed.unusable.has(key))
|
|
13967
|
+
return { key, value: "", path, unusable: true };
|
|
13968
|
+
const value = parsed.values.get(key)?.trim();
|
|
13969
|
+
if (value)
|
|
13970
|
+
return { key, value, path };
|
|
13971
|
+
}
|
|
13972
|
+
}
|
|
13973
|
+
return null;
|
|
13974
|
+
}
|
|
13975
|
+
function assertUsableCredential(appName, source, value) {
|
|
13976
|
+
if (VAULT_POINTER_SHAPE.test(value)) {
|
|
13977
|
+
throw new CredentialResolutionError(appName, `The credential from ${source} looks like a secrets-vault pointer (a path-shaped reference like ` + `'namespace/app/live/api_key'). A vault path is NEVER accepted as a literal API key. ` + `Use ${credentialPointerEnvKey2(appName)} to resolve the key through the vault, or provide the actual key value.`, [source]);
|
|
13978
|
+
}
|
|
13979
|
+
if (!ILLEGAL_IN_HEADER_VALUE.test(value))
|
|
13980
|
+
return;
|
|
13981
|
+
throw new CredentialResolutionError(appName, `The credential from ${source} contains characters that cannot be sent in an HTTP header ` + `(a control character or non-ASCII byte). A file written with CR-only line endings is the usual ` + `cause. Rewrite that credential file with one LF-terminated KEY=value line. ` + `The value is not shown here, and is deliberately never logged.`, [source]);
|
|
13982
|
+
}
|
|
13983
|
+
function sealCredential(fields) {
|
|
13984
|
+
const { apiKey } = fields;
|
|
13985
|
+
const visible = {
|
|
13986
|
+
tier: fields.tier,
|
|
13987
|
+
source: fields.source,
|
|
13988
|
+
deliberate: fields.deliberate,
|
|
13989
|
+
diskCandidates: Object.freeze([...fields.diskCandidates]),
|
|
13990
|
+
warning: fields.warning
|
|
13991
|
+
};
|
|
13992
|
+
const sealed = { ...visible };
|
|
13993
|
+
Object.defineProperty(sealed, "apiKey", {
|
|
13994
|
+
value: apiKey,
|
|
13995
|
+
enumerable: false,
|
|
13996
|
+
writable: false,
|
|
13997
|
+
configurable: false
|
|
13998
|
+
});
|
|
13999
|
+
if (fields.pointerVaultKey !== undefined) {
|
|
14000
|
+
Object.defineProperty(sealed, "pointerVaultKey", {
|
|
14001
|
+
value: fields.pointerVaultKey,
|
|
14002
|
+
enumerable: false,
|
|
14003
|
+
writable: false,
|
|
14004
|
+
configurable: false
|
|
14005
|
+
});
|
|
14006
|
+
}
|
|
14007
|
+
Object.defineProperty(sealed, INSPECT_CUSTOM2, {
|
|
14008
|
+
value: () => ({ ...visible, apiKey: "[redacted]" }),
|
|
14009
|
+
enumerable: false,
|
|
14010
|
+
writable: false,
|
|
14011
|
+
configurable: false
|
|
14012
|
+
});
|
|
14013
|
+
Object.defineProperty(sealed, CREDENTIAL_SEAL2, {
|
|
14014
|
+
value: true,
|
|
14015
|
+
enumerable: false,
|
|
14016
|
+
writable: false,
|
|
14017
|
+
configurable: false
|
|
14018
|
+
});
|
|
14019
|
+
return Object.freeze(sealed);
|
|
14020
|
+
}
|
|
14021
|
+
function isSealedCredential(credential) {
|
|
14022
|
+
return credential[CREDENTIAL_SEAL2] === true;
|
|
14023
|
+
}
|
|
14024
|
+
function explicitCredential(appName, apiKey) {
|
|
14025
|
+
const source = "explicit apiKey option";
|
|
14026
|
+
assertUsableCredential(appName, source, apiKey);
|
|
14027
|
+
return sealCredential({
|
|
14028
|
+
apiKey,
|
|
14029
|
+
tier: "argument",
|
|
14030
|
+
source,
|
|
14031
|
+
deliberate: true,
|
|
14032
|
+
diskCandidates: [],
|
|
14033
|
+
warning: null
|
|
14034
|
+
});
|
|
14035
|
+
}
|
|
14036
|
+
function validateAndSealResolvedCredential(appName, credential) {
|
|
14037
|
+
const apiKey = credential.apiKey;
|
|
14038
|
+
assertUsableCredential(appName, CALLER_SUPPLIED_CREDENTIAL_PROVIDER_SOURCE, apiKey);
|
|
14039
|
+
if (!isSealedCredential(credential)) {
|
|
14040
|
+
return sealCredential({
|
|
14041
|
+
apiKey,
|
|
14042
|
+
tier: "argument",
|
|
14043
|
+
source: CALLER_SUPPLIED_CREDENTIAL_PROVIDER_SOURCE,
|
|
14044
|
+
deliberate: true,
|
|
14045
|
+
diskCandidates: [],
|
|
14046
|
+
warning: null
|
|
14047
|
+
});
|
|
14048
|
+
}
|
|
14049
|
+
return sealCredential({
|
|
14050
|
+
apiKey,
|
|
14051
|
+
tier: credential.tier,
|
|
14052
|
+
source: credential.source,
|
|
14053
|
+
deliberate: credential.deliberate,
|
|
14054
|
+
diskCandidates: credential.diskCandidates,
|
|
14055
|
+
warning: credential.warning,
|
|
14056
|
+
...credential.pointerVaultKey !== undefined ? { pointerVaultKey: credential.pointerVaultKey } : {}
|
|
14057
|
+
});
|
|
14058
|
+
}
|
|
14059
|
+
function firstEnvValue(env, keys) {
|
|
14060
|
+
for (const key of keys) {
|
|
14061
|
+
if (!Object.prototype.hasOwnProperty.call(env, key))
|
|
14062
|
+
continue;
|
|
14063
|
+
const value = env[key]?.trim();
|
|
14064
|
+
if (value)
|
|
14065
|
+
return { key, value };
|
|
14066
|
+
}
|
|
14067
|
+
return null;
|
|
14068
|
+
}
|
|
14069
|
+
function isAmbientEnvironment(env) {
|
|
14070
|
+
return env === process.env || env[AMBIENT_ENVIRONMENT2] === true;
|
|
14071
|
+
}
|
|
14072
|
+
function defaultKeychainRunner(argv) {
|
|
14073
|
+
const result = spawnSync(KEYCHAIN_SECURITY_BIN, [...argv], {
|
|
14074
|
+
encoding: "utf8",
|
|
14075
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
14076
|
+
timeout: KEYCHAIN_SPAWN_TIMEOUT_MS
|
|
14077
|
+
});
|
|
14078
|
+
return {
|
|
14079
|
+
status: result.status,
|
|
14080
|
+
stdout: result.stdout ?? "",
|
|
14081
|
+
stderr: result.error ? result.error.message : result.stderr ?? ""
|
|
14082
|
+
};
|
|
14083
|
+
}
|
|
14084
|
+
function keychainTierEnabled(env, options) {
|
|
14085
|
+
if ((options.platform ?? process.platform) !== "darwin")
|
|
14086
|
+
return false;
|
|
14087
|
+
if (options.enabled !== undefined)
|
|
14088
|
+
return options.enabled;
|
|
14089
|
+
return options.run !== undefined || isAmbientEnvironment(env);
|
|
14090
|
+
}
|
|
14091
|
+
function keychainAccount(env, options) {
|
|
14092
|
+
const station = env[KEYCHAIN_STATION_ENV_KEY]?.trim();
|
|
14093
|
+
if (station)
|
|
14094
|
+
return station;
|
|
14095
|
+
const host = (options.hostname ?? osHostname)().split(".")[0]?.trim() ?? "";
|
|
14096
|
+
if (host)
|
|
14097
|
+
return host;
|
|
14098
|
+
const user = env.USER?.trim();
|
|
14099
|
+
return user || null;
|
|
14100
|
+
}
|
|
14101
|
+
function keychainFailureHint(text) {
|
|
14102
|
+
const line = text.split(/\r?\n/).find((entry) => entry.trim().length > 0)?.trim() ?? "";
|
|
14103
|
+
const clean = line.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 200);
|
|
14104
|
+
return clean ? `: ${clean}` : "";
|
|
14105
|
+
}
|
|
14106
|
+
function readKeychainItem(name, env, kind, options) {
|
|
14107
|
+
if (!SAFE_APP_SLUG.test(name) || !keychainTierEnabled(env, options))
|
|
14108
|
+
return null;
|
|
14109
|
+
const account = keychainAccount(env, options);
|
|
14110
|
+
if (!account)
|
|
14111
|
+
return null;
|
|
14112
|
+
const service = `${KEYCHAIN_SERVICE_PREFIX}.${name}.${kind}`;
|
|
14113
|
+
const source = `keychain:${service}@${account}`;
|
|
14114
|
+
const run = options.run ?? defaultKeychainRunner;
|
|
14115
|
+
let result;
|
|
14116
|
+
try {
|
|
14117
|
+
result = run(["find-generic-password", "-a", account, "-s", service, "-w"]);
|
|
14118
|
+
} catch (error) {
|
|
14119
|
+
const reason = keychainFailureHint(error instanceof Error ? error.message : String(error));
|
|
14120
|
+
throw new CredentialResolutionError(name, `The Keychain lookup for ${source} could not run${reason}. A Keychain failure is never resolved ` + `around: fix the keychain, or delete the item to fall through to the credential on disk.`, [source]);
|
|
14121
|
+
}
|
|
14122
|
+
if (result.status === KEYCHAIN_ITEM_NOT_FOUND_STATUS)
|
|
14123
|
+
return null;
|
|
14124
|
+
if (result.status !== 0) {
|
|
14125
|
+
throw new CredentialResolutionError(name, `The Keychain lookup for ${source} failed (security exited ` + `${result.status ?? "without a status"}${keychainFailureHint(result.stderr)}). A Keychain item that ` + `exists but cannot be read is never resolved around: unlock the keychain, run from a session that ` + `may use it, or delete the item to fall through to the credential on disk.`, [source]);
|
|
14126
|
+
}
|
|
14127
|
+
const value = result.stdout.trim();
|
|
14128
|
+
if (!value) {
|
|
14129
|
+
throw new CredentialResolutionError(name, `${source} exists but holds an empty value; a declared item never falls through to another ` + `identity. Store a value in it or delete the item.`, [source]);
|
|
14130
|
+
}
|
|
14131
|
+
return { value, source };
|
|
14132
|
+
}
|
|
14133
|
+
function keychainConfigValue(name, env, options = {}) {
|
|
14134
|
+
return readKeychainItem(name, env, "api-url", options);
|
|
14135
|
+
}
|
|
14136
|
+
function snapshotClientEnvironment(name, env) {
|
|
14137
|
+
const keys = clientTransportEnvKeys2(name);
|
|
14138
|
+
const ambient = isAmbientEnvironment(env);
|
|
14139
|
+
const snapshot = Object.create(null);
|
|
14140
|
+
for (const key of [
|
|
14141
|
+
...keys.apiUrlKeys,
|
|
14142
|
+
...keys.apiKeyKeys,
|
|
14143
|
+
credentialOverrideEnvKey2(name),
|
|
14144
|
+
credentialPointerEnvKey2(name),
|
|
14145
|
+
CREDENTIAL_PROFILE_ENV_KEY2,
|
|
14146
|
+
"HOME",
|
|
14147
|
+
HASNA_HOME_ENV_KEY,
|
|
14148
|
+
HASNA_CONFIG_HOME_ENV_KEY,
|
|
14149
|
+
KEYCHAIN_STATION_ENV_KEY,
|
|
14150
|
+
"USER"
|
|
14151
|
+
]) {
|
|
14152
|
+
const descriptor = Object.getOwnPropertyDescriptor(env, key);
|
|
14153
|
+
if (!descriptor)
|
|
14154
|
+
continue;
|
|
14155
|
+
if (!("value" in descriptor)) {
|
|
14156
|
+
throw new CredentialResolutionError(name, `${key} is accessor-backed; client configuration requires own data properties.`, [key]);
|
|
14157
|
+
}
|
|
14158
|
+
if (descriptor.value !== undefined && typeof descriptor.value !== "string") {
|
|
14159
|
+
throw new CredentialResolutionError(name, `${key} must be a string data property.`, [key]);
|
|
14160
|
+
}
|
|
14161
|
+
snapshot[key] = descriptor.value;
|
|
14162
|
+
}
|
|
14163
|
+
if (ambient) {
|
|
14164
|
+
Object.defineProperty(snapshot, AMBIENT_ENVIRONMENT2, {
|
|
14165
|
+
value: true,
|
|
14166
|
+
enumerable: false,
|
|
14167
|
+
writable: false,
|
|
14168
|
+
configurable: false
|
|
14169
|
+
});
|
|
14170
|
+
}
|
|
14171
|
+
return Object.freeze(snapshot);
|
|
14172
|
+
}
|
|
14173
|
+
function resolveCredential(name, env, options = {}) {
|
|
14174
|
+
env = snapshotClientEnvironment(name, env);
|
|
14175
|
+
const { apiKeyKeys } = clientTransportEnvKeys2(name);
|
|
14176
|
+
const diskPaths = credentialDiskSources(name, env);
|
|
14177
|
+
if (options.apiKey !== undefined) {
|
|
14178
|
+
const explicitKey = options.apiKey.trim();
|
|
14179
|
+
if (!explicitKey) {
|
|
14180
|
+
throw new CredentialResolutionError(name, "The explicit apiKey argument is blank; an explicit credential never falls through to another identity.", ["explicit apiKey argument"]);
|
|
14181
|
+
}
|
|
14182
|
+
assertUsableCredential(name, "the explicit apiKey argument", explicitKey);
|
|
14183
|
+
return sealCredential({
|
|
14184
|
+
apiKey: explicitKey,
|
|
14185
|
+
tier: "argument",
|
|
14186
|
+
source: "explicit apiKey argument",
|
|
14187
|
+
deliberate: true,
|
|
14188
|
+
diskCandidates: diskPaths,
|
|
14189
|
+
warning: null
|
|
14190
|
+
});
|
|
14191
|
+
}
|
|
14192
|
+
const overrideKeyName = credentialOverrideEnvKey2(name);
|
|
14193
|
+
const overrideRaw = Object.prototype.hasOwnProperty.call(env, overrideKeyName) ? env[overrideKeyName] : undefined;
|
|
14194
|
+
if (overrideRaw !== undefined) {
|
|
14195
|
+
const override = overrideRaw.trim();
|
|
14196
|
+
if (!override) {
|
|
14197
|
+
throw new CredentialResolutionError(name, `${overrideKeyName} is set but empty. It is a deliberate override, so it is not resolved around: ` + `either give it a real key or unset it to fall back to the credential on disk.`, [overrideKeyName]);
|
|
14198
|
+
}
|
|
14199
|
+
assertUsableCredential(name, overrideKeyName, override);
|
|
14200
|
+
return sealCredential({
|
|
14201
|
+
apiKey: override,
|
|
14202
|
+
tier: "override",
|
|
14203
|
+
source: overrideKeyName,
|
|
14204
|
+
deliberate: true,
|
|
14205
|
+
diskCandidates: diskPaths,
|
|
14206
|
+
warning: null
|
|
14207
|
+
});
|
|
14208
|
+
}
|
|
14209
|
+
const pointerKeyName = credentialPointerEnvKey2(name);
|
|
14210
|
+
const pointerRaw = Object.prototype.hasOwnProperty.call(env, pointerKeyName) ? env[pointerKeyName] : undefined;
|
|
14211
|
+
if (pointerRaw !== undefined) {
|
|
14212
|
+
const pointer = pointerRaw.trim();
|
|
14213
|
+
if (!pointer) {
|
|
14214
|
+
throw new CredentialResolutionError(name, `${pointerKeyName} is set but empty. It is a deliberate vault pointer, so it is not resolved around: ` + `either give it a vault item key or unset it to fall back to the credential on disk.`, [pointerKeyName]);
|
|
14215
|
+
}
|
|
14216
|
+
if (!VAULT_POINTER_SHAPE.test(pointer)) {
|
|
14217
|
+
throw new CredentialResolutionError(name, `${pointerKeyName} must name a vault ITEM KEY (a path-shaped reference like ` + `'namespace/app/live/api_key'), not a credential value. A pointer that carries a literal is refused.`, [pointerKeyName]);
|
|
14218
|
+
}
|
|
14219
|
+
return sealCredential({
|
|
14220
|
+
apiKey: "",
|
|
14221
|
+
pointerVaultKey: pointer,
|
|
14222
|
+
tier: "pointer",
|
|
14223
|
+
source: pointerKeyName,
|
|
14224
|
+
deliberate: true,
|
|
14225
|
+
diskCandidates: diskPaths,
|
|
14226
|
+
warning: null
|
|
14227
|
+
});
|
|
14228
|
+
}
|
|
14229
|
+
if (options.profile !== undefined && !options.profile.trim()) {
|
|
14230
|
+
throw new CredentialResolutionError(name, "The explicit profile argument is blank; an explicit identity selection never falls through.", ["explicit profile argument"]);
|
|
14231
|
+
}
|
|
14232
|
+
const profileRaw = Object.prototype.hasOwnProperty.call(env, CREDENTIAL_PROFILE_ENV_KEY2) ? env[CREDENTIAL_PROFILE_ENV_KEY2] : undefined;
|
|
14233
|
+
if (profileRaw !== undefined && !profileRaw.trim()) {
|
|
14234
|
+
throw new CredentialResolutionError(name, `${CREDENTIAL_PROFILE_ENV_KEY2} is set but blank.`, [CREDENTIAL_PROFILE_ENV_KEY2]);
|
|
14235
|
+
}
|
|
14236
|
+
const profile = options.profile?.trim() || profileRaw?.trim();
|
|
14237
|
+
if (profile) {
|
|
14238
|
+
const profileSource = options.profile?.trim() ? "explicit profile argument" : CREDENTIAL_PROFILE_ENV_KEY2;
|
|
14239
|
+
if (!SAFE_PROFILE.test(profile)) {
|
|
14240
|
+
throw new CredentialResolutionError(name, `Profile name from ${profileSource} is not usable in a path. ` + `Use letters, digits, dot, dash, or underscore.`, [profileSource]);
|
|
14241
|
+
}
|
|
14242
|
+
const paths = profileDiskSources(name, env, profile);
|
|
14243
|
+
for (const path of paths) {
|
|
14244
|
+
const value = readCredentialFile(path, apiKeyKeys);
|
|
14245
|
+
if (value) {
|
|
14246
|
+
assertUsableCredential(name, path, value);
|
|
14247
|
+
return sealCredential({
|
|
14248
|
+
apiKey: value,
|
|
14249
|
+
tier: "profile",
|
|
14250
|
+
source: path,
|
|
14251
|
+
deliberate: true,
|
|
14252
|
+
diskCandidates: paths,
|
|
14253
|
+
warning: null
|
|
14254
|
+
});
|
|
14255
|
+
}
|
|
14256
|
+
}
|
|
14257
|
+
throw new CredentialResolutionError(name, `Profile '${profile}' (from ${profileSource}) has no ${apiKeyKeys[0]} for '${name}'. ` + `Looked in: ${paths.join(", ") || "<no HOME in this environment>"}. ` + `A profile names WHICH identity to use, so it is never resolved around \u2014 ` + `create the profile's credential file or unset ${CREDENTIAL_PROFILE_ENV_KEY2}.`, paths);
|
|
14258
|
+
}
|
|
14259
|
+
const definedEnvEntries = apiKeyKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, value: String(env[key]).trim() }));
|
|
14260
|
+
const blankEnv = definedEnvEntries.find((entry) => entry.value.length === 0);
|
|
14261
|
+
if (blankEnv) {
|
|
14262
|
+
throw new CredentialResolutionError(name, `${blankEnv.key} is set but blank; a declared credential never falls through to another alias or identity.`, [blankEnv.key]);
|
|
14263
|
+
}
|
|
14264
|
+
if (definedEnvEntries.length > 1 && new Set(definedEnvEntries.map((entry) => entry.value)).size > 1) {
|
|
14265
|
+
throw new CredentialResolutionError(name, `${definedEnvEntries.map((entry) => entry.key).join(" and ")} disagree; credential aliases must be identical or only one may be set.`, definedEnvEntries.map((entry) => entry.key));
|
|
14266
|
+
}
|
|
14267
|
+
const envHit = firstEnvValue(env, apiKeyKeys);
|
|
14268
|
+
const keychainHit = readKeychainItem(name, env, "api-key", options.keychain ?? {});
|
|
14269
|
+
if (keychainHit) {
|
|
14270
|
+
assertUsableCredential(name, keychainHit.source, keychainHit.value);
|
|
14271
|
+
const warning = envHit && envHit.value !== keychainHit.value ? `Credential sources disagree for '${name}': ${keychainHit.source} and ${envHit.key} hold ` + `different keys. ${keychainHit.source} wins, because the Keychain is re-read on every call while ` + `an environment variable is a snapshot. Reconcile them \u2014 a rotation that updated only one leaves ` + `the other to fail 401 wherever it is loaded first.` : null;
|
|
14272
|
+
return sealCredential({
|
|
14273
|
+
apiKey: keychainHit.value,
|
|
14274
|
+
tier: "keychain",
|
|
14275
|
+
source: keychainHit.source,
|
|
14276
|
+
deliberate: false,
|
|
14277
|
+
diskCandidates: diskPaths,
|
|
14278
|
+
warning
|
|
14279
|
+
});
|
|
14280
|
+
}
|
|
14281
|
+
const diskSourceList = credentialDiskSourceList(name, env, null);
|
|
14282
|
+
const diskHits = diskSourceList.map((src) => ({ src, value: readCredentialFile(src.path, apiKeyKeys) })).filter((hit) => hit.value !== null);
|
|
14283
|
+
if (diskHits.length > 0) {
|
|
14284
|
+
const winner = diskHits[0];
|
|
14285
|
+
assertUsableCredential(name, winner.src.path, winner.value);
|
|
14286
|
+
const divergentSources = [
|
|
14287
|
+
...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.src.path),
|
|
14288
|
+
...envHit && envHit.value !== winner.value ? [envHit.key] : []
|
|
14289
|
+
];
|
|
14290
|
+
const warning = divergentSources.length > 0 ? `Credential sources disagree for '${name}': ${winner.src.path} and ` + `${divergentSources.join(", ")} hold different keys. ${winner.src.path} wins, because a file on ` + `disk is re-read on every call while an environment variable is a snapshot. Reconcile them \u2014 ` + `a rotation that updated only one leaves the other to fail 401 wherever it is loaded first.` : null;
|
|
14291
|
+
return sealCredential({
|
|
14292
|
+
apiKey: winner.value,
|
|
14293
|
+
tier: winner.src.tier,
|
|
14294
|
+
source: winner.src.path,
|
|
14295
|
+
deliberate: false,
|
|
14296
|
+
diskCandidates: diskPaths,
|
|
14297
|
+
warning
|
|
14298
|
+
});
|
|
14299
|
+
}
|
|
14300
|
+
if (envHit) {
|
|
14301
|
+
assertUsableCredential(name, envHit.key, envHit.value);
|
|
14302
|
+
return sealCredential({
|
|
14303
|
+
apiKey: envHit.value,
|
|
14304
|
+
tier: "env",
|
|
14305
|
+
source: envHit.key,
|
|
14306
|
+
deliberate: false,
|
|
14307
|
+
diskCandidates: diskPaths,
|
|
14308
|
+
warning: null
|
|
14309
|
+
});
|
|
14310
|
+
}
|
|
14311
|
+
return null;
|
|
14312
|
+
}
|
|
14313
|
+
async function completePointerCredential(name, pointerResolution, env = process.env) {
|
|
14314
|
+
const vaultKey = pointerResolution.pointerVaultKey;
|
|
14315
|
+
const pointerEnvKey = pointerResolution.source;
|
|
14316
|
+
if (!vaultKey) {
|
|
14317
|
+
throw new CredentialResolutionError(name, `Pointer resolution from ${pointerEnvKey} carries no vault item key; this is a defect in the resolver.`, [pointerEnvKey]);
|
|
14318
|
+
}
|
|
14319
|
+
let secretsSdk;
|
|
14320
|
+
try {
|
|
14321
|
+
secretsSdk = requireSecretsSdk2(SECRETS_PACKAGE_SPECIFIER2);
|
|
14322
|
+
} catch {
|
|
14323
|
+
throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets SDK (@hasna/secrets) is not installed ` + `in this process. A vault pointer is TERMINAL: install @hasna/secrets to resolve it, or unset ${pointerEnvKey}.`, [pointerEnvKey]);
|
|
14324
|
+
}
|
|
14325
|
+
let client;
|
|
14326
|
+
try {
|
|
14327
|
+
client = secretsSdk.createSecretsClientFromEnv(env);
|
|
14328
|
+
} catch {
|
|
14329
|
+
throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the secrets client could not be configured from this ` + `environment (the secrets service URL and key env are missing or invalid). A vault pointer is TERMINAL and ` + `never falls through to a literal or disk credential.`, [pointerEnvKey]);
|
|
14330
|
+
}
|
|
14331
|
+
let secret;
|
|
14332
|
+
try {
|
|
14333
|
+
secret = await client.getSecret({ key: vaultKey });
|
|
14334
|
+
} catch {
|
|
14335
|
+
throw new CredentialResolutionError(name, `${pointerEnvKey} names vault item '${vaultKey}', but the vault could not be reached or the item is ` + `unavailable. A vault pointer is TERMINAL and never falls through to a literal or disk credential.`, [pointerEnvKey]);
|
|
14336
|
+
}
|
|
14337
|
+
const value = secret.value;
|
|
14338
|
+
if (!value) {
|
|
14339
|
+
throw new CredentialResolutionError(name, `${pointerEnvKey} resolved vault item '${vaultKey}', but it holds no value. A vault pointer is TERMINAL.`, [pointerEnvKey]);
|
|
14340
|
+
}
|
|
14341
|
+
assertUsableCredential(name, `${pointerEnvKey} -> vault:${vaultKey}`, value);
|
|
14342
|
+
return sealCredential({
|
|
14343
|
+
apiKey: value,
|
|
14344
|
+
tier: "pointer",
|
|
14345
|
+
source: `${pointerEnvKey} -> vault:${vaultKey}`,
|
|
14346
|
+
deliberate: true,
|
|
14347
|
+
diskCandidates: pointerResolution.diskCandidates,
|
|
14348
|
+
warning: null
|
|
14349
|
+
});
|
|
14350
|
+
}
|
|
14351
|
+
function defaultFleetGatewayBaseUrl(name) {
|
|
14352
|
+
return `${DEFAULT_FLEET_GATEWAY_ORIGIN}/${validateAppSlug(name)}`;
|
|
14353
|
+
}
|
|
14354
|
+
function isValidDnsDomain(value) {
|
|
14355
|
+
if (value.length === 0 || value.length > 253 || ASCII_CONTROL_PATTERN.test(value) || /[^\x00-\x7f]/.test(value)) {
|
|
14356
|
+
return false;
|
|
14357
|
+
}
|
|
14358
|
+
return value.split(".").every((label) => label.length <= 63 && !label.startsWith("xn--") && DNS_LABEL_PATTERN.test(label));
|
|
14359
|
+
}
|
|
14360
|
+
function validateAppSlug(name) {
|
|
14361
|
+
if (name.length > 63 || !DNS_LABEL_PATTERN.test(name)) {
|
|
14362
|
+
throw new Error("App name must be one lowercase DNS label.");
|
|
14363
|
+
}
|
|
14364
|
+
return name;
|
|
14365
|
+
}
|
|
14366
|
+
function rawAuthority(value) {
|
|
14367
|
+
const match = /^[a-z][a-z0-9+.-]*:\/\//i.exec(value);
|
|
14368
|
+
if (!match)
|
|
14369
|
+
throw new Error("API URL must be absolute.");
|
|
14370
|
+
const afterScheme = value.slice(match[0].length);
|
|
14371
|
+
const boundary = afterScheme.search(/[/?#]/);
|
|
14372
|
+
const authority = boundary === -1 ? afterScheme : afterScheme.slice(0, boundary);
|
|
14373
|
+
if (!authority)
|
|
14374
|
+
throw new Error("API URL must include a hostname.");
|
|
14375
|
+
return authority;
|
|
14376
|
+
}
|
|
14377
|
+
function assertCanonicalPort(port) {
|
|
14378
|
+
if (!/^[0-9]+$/.test(port) || port.length > 1 && port.startsWith("0")) {
|
|
14379
|
+
throw new Error("API URL authority must contain a canonical port between 1 and 65535.");
|
|
14380
|
+
}
|
|
14381
|
+
const numericPort = Number(port);
|
|
14382
|
+
if (!Number.isSafeInteger(numericPort) || numericPort < 1 || numericPort > 65535) {
|
|
14383
|
+
throw new Error("API URL authority must contain a canonical port between 1 and 65535.");
|
|
14384
|
+
}
|
|
14385
|
+
}
|
|
14386
|
+
function canonicalAuthorityHostname(authority) {
|
|
14387
|
+
let rawHostname;
|
|
14388
|
+
if (authority.startsWith("[")) {
|
|
14389
|
+
const closingBracket = authority.indexOf("]");
|
|
14390
|
+
if (closingBracket === -1) {
|
|
14391
|
+
throw new Error("API URL authority must contain a canonical hostname.");
|
|
14392
|
+
}
|
|
14393
|
+
rawHostname = authority.slice(0, closingBracket + 1);
|
|
14394
|
+
const portSuffix = authority.slice(closingBracket + 1);
|
|
14395
|
+
if (portSuffix) {
|
|
14396
|
+
if (!portSuffix.startsWith(":")) {
|
|
14397
|
+
throw new Error("API URL authority must contain a canonical hostname and port.");
|
|
14398
|
+
}
|
|
14399
|
+
assertCanonicalPort(portSuffix.slice(1));
|
|
14400
|
+
}
|
|
14401
|
+
if (isIP2(rawHostname.slice(1, -1)) !== 6) {
|
|
14402
|
+
throw new Error("API URL authority must contain a canonical IPv6 literal.");
|
|
14403
|
+
}
|
|
14404
|
+
} else {
|
|
14405
|
+
const firstColon = authority.indexOf(":");
|
|
14406
|
+
const lastColon = authority.lastIndexOf(":");
|
|
14407
|
+
if (firstColon !== lastColon) {
|
|
14408
|
+
throw new Error("IPv6 API URL authorities must use brackets.");
|
|
14409
|
+
}
|
|
14410
|
+
if (lastColon !== -1) {
|
|
14411
|
+
const port = authority.slice(lastColon + 1);
|
|
14412
|
+
assertCanonicalPort(port);
|
|
14413
|
+
rawHostname = authority.slice(0, lastColon);
|
|
14414
|
+
} else {
|
|
14415
|
+
rawHostname = authority;
|
|
14416
|
+
}
|
|
14417
|
+
const ipVersion = isIP2(rawHostname);
|
|
14418
|
+
const numericAddressParts = rawHostname.split(".");
|
|
14419
|
+
const looksLikeNonCanonicalIpv4 = numericAddressParts.every((part) => /^(?:0x[0-9a-f]+|[0-9]+)$/i.test(part));
|
|
14420
|
+
if (ipVersion !== 4 && looksLikeNonCanonicalIpv4 || ipVersion !== 4 && !isValidDnsDomain(rawHostname.toLowerCase())) {
|
|
14421
|
+
throw new Error("API URL authority must contain a canonical ASCII hostname.");
|
|
14422
|
+
}
|
|
14423
|
+
}
|
|
14424
|
+
return rawHostname.toLowerCase();
|
|
14425
|
+
}
|
|
14426
|
+
function isDeliberateLoopbackHttpAuthority(authority) {
|
|
14427
|
+
return /^(?:localhost|127\.0\.0\.1|\[::1\])(?::[0-9]+)?$/i.test(authority);
|
|
14428
|
+
}
|
|
14429
|
+
function toV1BaseUrl(apiUrl) {
|
|
14430
|
+
if (ASCII_CONTROL_PATTERN.test(apiUrl)) {
|
|
14431
|
+
throw new Error("API URL must not contain ASCII control characters.");
|
|
14432
|
+
}
|
|
14433
|
+
const input = apiUrl.trim();
|
|
14434
|
+
const authority = rawAuthority(input);
|
|
14435
|
+
if (authority.includes("@") || authority.includes("\\") || authority.includes("%") || /[^\x00-\x7f]/.test(authority)) {
|
|
14436
|
+
throw new Error("API URL authority must be canonical ASCII without credentials.");
|
|
14437
|
+
}
|
|
14438
|
+
const canonicalHostname = canonicalAuthorityHostname(authority);
|
|
14439
|
+
const url = new URL(input);
|
|
14440
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
14441
|
+
throw new Error("API URL must use http or https.");
|
|
14442
|
+
}
|
|
14443
|
+
if (url.username || url.password) {
|
|
14444
|
+
throw new Error("API URL must not include credentials.");
|
|
14445
|
+
}
|
|
14446
|
+
if (!url.hostname || url.hostname.endsWith(".")) {
|
|
14447
|
+
throw new Error("API URL must include a canonical hostname.");
|
|
14448
|
+
}
|
|
14449
|
+
if (url.hostname.toLowerCase() !== canonicalHostname) {
|
|
14450
|
+
throw new Error("API URL authority must not rely on parser hostname normalization.");
|
|
14451
|
+
}
|
|
14452
|
+
if (url.hostname.split(".").some((label) => label.toLowerCase().startsWith("xn--"))) {
|
|
14453
|
+
throw new Error("API URL must not use IDN or punycode hostnames.");
|
|
14454
|
+
}
|
|
14455
|
+
if (url.protocol === "http:" && !isDeliberateLoopbackHttpAuthority(authority)) {
|
|
14456
|
+
throw new Error("API URL may use http only for an exact loopback authority.");
|
|
14457
|
+
}
|
|
14458
|
+
if (url.search || url.hash) {
|
|
14459
|
+
throw new Error("API URL must not include a query string or fragment.");
|
|
14460
|
+
}
|
|
14461
|
+
let path = url.pathname.replace(/\/+$/, "");
|
|
14462
|
+
if (path.endsWith("/v1"))
|
|
14463
|
+
path = path.slice(0, -"/v1".length);
|
|
14464
|
+
url.pathname = `${path}/v1`;
|
|
14465
|
+
return url.toString().replace(/\/+$/, "");
|
|
14466
|
+
}
|
|
14467
|
+
function resolveClientTransportSnapshot(name, env = process.env, options = {}) {
|
|
14468
|
+
env = snapshotClientEnvironment(name, env);
|
|
14469
|
+
const keys = clientTransportEnvKeys2(name);
|
|
14470
|
+
const definedUrlEntries = keys.apiUrlKeys.filter((key) => Object.prototype.hasOwnProperty.call(env, key) && env[key] !== undefined).map((key) => ({ key, raw: String(env[key]) }));
|
|
14471
|
+
const blankUrl = definedUrlEntries.find((entry) => entry.raw.trim().length === 0);
|
|
14472
|
+
if (blankUrl) {
|
|
14473
|
+
throw new ClientTransportConfigurationError(name, `${blankUrl.key} is set but blank; public clients require an explicit HTTPS API URL and never select local storage.`, [blankUrl.key]);
|
|
14474
|
+
}
|
|
14475
|
+
const controlledUrl = definedUrlEntries.find((entry) => ASCII_CONTROL_PATTERN.test(entry.raw));
|
|
14476
|
+
if (controlledUrl) {
|
|
14477
|
+
throw new ClientTransportConfigurationError(name, `${controlledUrl.key} contains ASCII control characters.`, [controlledUrl.key]);
|
|
14478
|
+
}
|
|
14479
|
+
const usableUrlEntries = definedUrlEntries.map((entry) => ({ key: entry.key, value: entry.raw.trim() }));
|
|
14480
|
+
if (usableUrlEntries.length > 1 && new Set(usableUrlEntries.map((entry) => entry.value)).size > 1) {
|
|
14481
|
+
throw new ClientTransportConfigurationError(name, `${usableUrlEntries.map((entry) => entry.key).join(" and ")} disagree; client authority aliases must be identical or only one may be set.`, usableUrlEntries.map((entry) => entry.key));
|
|
14482
|
+
}
|
|
14483
|
+
const envUrlHit = usableUrlEntries[0] ?? null;
|
|
14484
|
+
const keychainUrlHit = keychainConfigValue(name, env, options.credentials?.keychain);
|
|
14485
|
+
const diskConfigUrlHit = appConfigDiskValue(name, env, keys.apiUrlKeys);
|
|
14486
|
+
if (diskConfigUrlHit?.unusable) {
|
|
14487
|
+
throw new ClientTransportConfigurationError(name, `${diskConfigUrlHit.key} in ${diskConfigUrlHit.path} is declared but blank or malformed; public clients require a valid HTTPS service authority.`, [diskConfigUrlHit.path]);
|
|
14488
|
+
}
|
|
14489
|
+
const urlCandidates = [
|
|
14490
|
+
...envUrlHit ? [envUrlHit] : [],
|
|
14491
|
+
...keychainUrlHit ? [{ key: keychainUrlHit.source, value: keychainUrlHit.value }] : [],
|
|
14492
|
+
...diskConfigUrlHit ? [{ key: diskConfigUrlHit.path, value: diskConfigUrlHit.value.trim() }] : []
|
|
14493
|
+
];
|
|
14494
|
+
const configuredUrl = urlCandidates[0] ?? null;
|
|
14495
|
+
const divergentUrls = urlCandidates.filter((candidate) => candidate.value !== configuredUrl?.value);
|
|
14496
|
+
if (configuredUrl && divergentUrls.length > 0) {
|
|
14497
|
+
throw new ClientTransportConfigurationError(name, `${configuredUrl.key} and ${divergentUrls.map((candidate) => candidate.key).join(" and ")} select different service authorities; refusing to send a credential written for one authority to the other.`, urlCandidates.map((candidate) => candidate.key));
|
|
14498
|
+
}
|
|
14499
|
+
const warnings = [];
|
|
14500
|
+
if (configuredUrl && !envUrlHit) {
|
|
14501
|
+
warnings.push(`No ${keys.apiUrlKeys[0]} in the environment; the server URL in ${configuredUrl.key} was used, so this client connects to the server. ` + `Keep that entry aligned with the intended service authority.`);
|
|
14502
|
+
}
|
|
14503
|
+
const credential = resolveCredential(name, env, options.credentials);
|
|
14504
|
+
if (!credential) {
|
|
14505
|
+
const diskHint = credentialDiskSourcesForMessage(name, env);
|
|
14506
|
+
const lead = configuredUrl ? `${configuredUrl.key} selects the HTTP server for '${name}', but no API key could be resolved` : `${keys.apiUrlKeys[0]} is not set and no API key could be resolved for '${name}'; a credential is required before the default fleet gateway authority applies`;
|
|
14507
|
+
warnings.push(`${lead}; refusing to create an unauthenticated client \u2014 public clients never fall back to SQLite or another local store. ` + `Looked in the Keychain (macOS only), then for a credential file at ${diskHint}, then for ${keys.apiKeyKeys[0]} in the environment.`);
|
|
14508
|
+
throw new ClientTransportConfigurationError(name, warnings.join(" "), [configuredUrl?.key ?? keys.apiUrlKeys[0]]);
|
|
14509
|
+
}
|
|
14510
|
+
if (credential.warning)
|
|
14511
|
+
warnings.push(credential.warning);
|
|
14512
|
+
let urlHit;
|
|
14513
|
+
if (configuredUrl) {
|
|
14514
|
+
urlHit = configuredUrl;
|
|
14515
|
+
} else {
|
|
14516
|
+
try {
|
|
14517
|
+
urlHit = { key: DEFAULT_AUTHORITY_SOURCE, value: defaultFleetGatewayBaseUrl(name) };
|
|
14518
|
+
} catch (error) {
|
|
14519
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
14520
|
+
throw new ClientTransportConfigurationError(name, `No ${keys.apiUrlKeys[0]} is configured and the default fleet gateway authority cannot be composed for '${name}': ${message}`, [keys.apiUrlKeys[0]]);
|
|
14521
|
+
}
|
|
14522
|
+
}
|
|
14523
|
+
const apiUrlSource = urlHit.key;
|
|
14524
|
+
let baseUrl;
|
|
14525
|
+
try {
|
|
14526
|
+
baseUrl = toV1BaseUrl(urlHit.value);
|
|
14527
|
+
} catch (error) {
|
|
14528
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
14529
|
+
throw new ClientTransportConfigurationError(name, `Invalid API URL from ${apiUrlSource}: ${message}`, [apiUrlSource]);
|
|
14530
|
+
}
|
|
14531
|
+
return {
|
|
14532
|
+
resolution: {
|
|
14533
|
+
transport: "http",
|
|
14534
|
+
transportSource: urlHit.key,
|
|
14535
|
+
baseUrl,
|
|
14536
|
+
apiUrlSource,
|
|
14537
|
+
apiKeyPresent: true,
|
|
14538
|
+
apiKeySource: credential.source,
|
|
14539
|
+
apiKeyTier: credential.tier,
|
|
14540
|
+
misconfigured: false,
|
|
14541
|
+
warning: warnings.length > 0 ? warnings.join(" ") : null
|
|
14542
|
+
},
|
|
14543
|
+
credential
|
|
14544
|
+
};
|
|
14545
|
+
}
|
|
14546
|
+
function credentialDiskSourcesForMessage(name, env) {
|
|
14547
|
+
const paths = credentialDiskSources(name, env);
|
|
14548
|
+
return paths.length > 0 ? paths.join(" or ") : "<no HOME or HASNA_HOME set in this environment, so no credential file was consulted>";
|
|
14549
|
+
}
|
|
14550
|
+
function currentCredential(name, apiKey) {
|
|
14551
|
+
if (typeof apiKey === "function") {
|
|
14552
|
+
return validateAndSealResolvedCredential(name, apiKey());
|
|
14553
|
+
}
|
|
14554
|
+
return explicitCredential(name, apiKey);
|
|
14555
|
+
}
|
|
14556
|
+
async function resolveRequestCredential(name, apiKey, env = process.env) {
|
|
14557
|
+
const resolved = currentCredential(name, apiKey);
|
|
14558
|
+
if (resolved.tier === "pointer") {
|
|
14559
|
+
return completePointerCredential(name, resolved, env);
|
|
14560
|
+
}
|
|
14561
|
+
return resolved;
|
|
14562
|
+
}
|
|
14563
|
+
function authFailureGuidance(credential) {
|
|
14564
|
+
const origin = `The API key for this request came from ${credential.source}`;
|
|
14565
|
+
if (credential.deliberate) {
|
|
14566
|
+
const remedy = credential.source === CALLER_SUPPLIED_CREDENTIAL_PROVIDER_SOURCE ? `Fix that provider so it returns the current key, or replace it with resolveCredential() ` + `so diagnostics can name the original source.` : `Rotate that key, or unset the override to use the credential on disk.`;
|
|
14567
|
+
return `${origin} \u2014 a credential you selected deliberately. It was NOT substituted with any other key: ` + `falling back here would authenticate as a different principal than the one you named, which is ` + `exactly the failure an override exists to prevent. ${remedy}`;
|
|
14568
|
+
}
|
|
14569
|
+
if (credential.tier === "env") {
|
|
14570
|
+
const target = credential.diskCandidates[0];
|
|
14571
|
+
const remedy = target ? `Store the CURRENT key in the Keychain or write it to ${target} \u2014 both are re-read on every call, so ` + `rotations take effect immediately and in every shell. Do not simply unset ${credential.source}: ` + `nothing was found in the Keychain or on disk, so that would leave this client with no credential at all.` : `This environment has no HOME or HASNA_HOME, so no credential file could be consulted; the disk tier is ` + `unavailable here and there is nothing to fall back to. Set HOME, or supply the key explicitly.`;
|
|
14572
|
+
return `${origin}, a variable in this process's environment. If a wrapper injected it for this one process, the ` + `wrapper re-reads its store on every invocation and the stored key itself is being rejected \u2014 rotate it. ` + `If this SHELL exported it, the export is a snapshot taken when the shell started: a STALE SHELL that ` + `exported the key before it was rotated keeps sending the old one until it exits. ${remedy}`;
|
|
14573
|
+
}
|
|
14574
|
+
if (credential.tier === "keychain") {
|
|
14575
|
+
return `${origin}, which was re-read from the Keychain on this very call \u2014 so a stale shell is NOT the cause ` + `here. The stored item is genuinely being rejected: update it with the current key, or re-run the fleet ` + `key distribution so this machine gets the current key.`;
|
|
14576
|
+
}
|
|
14577
|
+
return `${origin}, which was re-read from disk on this very call \u2014 so a stale shell is NOT the cause here. ` + `The stored credential is genuinely being rejected: rotate it, or re-run the fleet key distribution ` + `so this machine gets the current key.`;
|
|
14578
|
+
}
|
|
14579
|
+
function assertNoAuthorityOverrideHeaders(headers, source) {
|
|
14580
|
+
if (!headers)
|
|
14581
|
+
return;
|
|
14582
|
+
const forbidden = Object.keys(headers).find((name) => AUTHORITY_OVERRIDE_HEADERS2.has(name.trim().toLowerCase()));
|
|
14583
|
+
if (forbidden) {
|
|
14584
|
+
throw new Error(`Authenticated ${source} headers must not set authority header '${forbidden}'.`);
|
|
14585
|
+
}
|
|
14586
|
+
}
|
|
14587
|
+
function appendQuery(path, query) {
|
|
14588
|
+
if (!query)
|
|
14589
|
+
return path;
|
|
14590
|
+
const params = query instanceof URLSearchParams ? query : new URLSearchParams;
|
|
14591
|
+
if (!(query instanceof URLSearchParams)) {
|
|
14592
|
+
for (const [key, value] of Object.entries(query)) {
|
|
14593
|
+
if (value === null || value === undefined)
|
|
14594
|
+
continue;
|
|
14595
|
+
if (Array.isArray(value)) {
|
|
14596
|
+
for (const v of value)
|
|
14597
|
+
params.append(key, String(v));
|
|
14598
|
+
} else {
|
|
14599
|
+
params.append(key, String(value));
|
|
14600
|
+
}
|
|
14601
|
+
}
|
|
14602
|
+
}
|
|
14603
|
+
const qs = params.toString();
|
|
14604
|
+
if (!qs)
|
|
14605
|
+
return path;
|
|
14606
|
+
return `${path}${path.includes("?") ? "&" : "?"}${qs}`;
|
|
14607
|
+
}
|
|
14608
|
+
function createHasnaHttpTransportInternal(options, requestBindingProvider) {
|
|
14609
|
+
const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
14610
|
+
const base = toV1BaseUrl(options.baseUrl);
|
|
14611
|
+
const timeoutMs = options.timeoutMs ?? 30000;
|
|
14612
|
+
const sleep = options.sleepImpl ?? defaultSleep;
|
|
14613
|
+
const defaultRetry = options.retry;
|
|
14614
|
+
function resolveRetry(callRetry) {
|
|
14615
|
+
const chosen = callRetry !== undefined ? callRetry : defaultRetry;
|
|
14616
|
+
if (chosen === false)
|
|
14617
|
+
return null;
|
|
14618
|
+
const r = chosen ?? {};
|
|
14619
|
+
return {
|
|
14620
|
+
retries: r.retries ?? 2,
|
|
14621
|
+
baseDelayMs: r.baseDelayMs ?? 200,
|
|
14622
|
+
maxDelayMs: r.maxDelayMs ?? 2000,
|
|
14623
|
+
retryStatuses: r.retryStatuses ?? [...DEFAULT_RETRY_STATUSES]
|
|
14624
|
+
};
|
|
14625
|
+
}
|
|
14626
|
+
async function once(method, rel, url, body, opts, credential) {
|
|
14627
|
+
assertNoAuthorityOverrideHeaders(options.headers, "transport");
|
|
14628
|
+
assertNoAuthorityOverrideHeaders(opts.headers, "request");
|
|
14629
|
+
const headers = {
|
|
14630
|
+
"x-api-key": credential.apiKey,
|
|
14631
|
+
Authorization: `Bearer ${credential.apiKey}`,
|
|
14632
|
+
Accept: "application/json",
|
|
14633
|
+
...options.headers ?? {},
|
|
14634
|
+
...opts.headers ?? {}
|
|
14635
|
+
};
|
|
14636
|
+
if (opts.idempotencyKey)
|
|
14637
|
+
headers["Idempotency-Key"] = opts.idempotencyKey;
|
|
14638
|
+
const init = {
|
|
14639
|
+
method,
|
|
14640
|
+
headers,
|
|
14641
|
+
redirect: "manual"
|
|
14642
|
+
};
|
|
14643
|
+
if (body !== undefined) {
|
|
14644
|
+
headers["Content-Type"] = "application/json";
|
|
14645
|
+
init.body = JSON.stringify(body);
|
|
14646
|
+
}
|
|
14647
|
+
const controller = new AbortController;
|
|
14648
|
+
const onAbort = () => controller.abort();
|
|
14649
|
+
if (opts.signal) {
|
|
14650
|
+
if (opts.signal.aborted)
|
|
14651
|
+
controller.abort();
|
|
14652
|
+
else
|
|
14653
|
+
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
14654
|
+
}
|
|
14655
|
+
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? timeoutMs);
|
|
14656
|
+
init.signal = controller.signal;
|
|
14657
|
+
let response;
|
|
14658
|
+
try {
|
|
14659
|
+
response = await fetchImpl(url, init);
|
|
14660
|
+
} catch (error) {
|
|
14661
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
14662
|
+
if (opts.signal?.aborted)
|
|
14663
|
+
return { ok: false, retryable: false, error: err };
|
|
14664
|
+
return { ok: false, retryable: true, error: err };
|
|
14665
|
+
} finally {
|
|
14666
|
+
clearTimeout(timer);
|
|
14667
|
+
if (opts.signal)
|
|
14668
|
+
opts.signal.removeEventListener("abort", onAbort);
|
|
14669
|
+
}
|
|
14670
|
+
const authenticationFailure = response.status === 401 || response.status === 403;
|
|
14671
|
+
let parsed = undefined;
|
|
14672
|
+
if (authenticationFailure) {
|
|
14673
|
+
try {
|
|
14674
|
+
await response.body?.cancel();
|
|
14675
|
+
} catch {}
|
|
14676
|
+
} else {
|
|
14677
|
+
const text = await response.text();
|
|
14678
|
+
if (text.length > 0) {
|
|
14679
|
+
try {
|
|
14680
|
+
parsed = JSON.parse(text);
|
|
14681
|
+
} catch {
|
|
14682
|
+
parsed = text;
|
|
14683
|
+
}
|
|
14684
|
+
}
|
|
14685
|
+
}
|
|
14686
|
+
if (!response.ok) {
|
|
14687
|
+
if (response.status >= 300 && response.status < 400) {
|
|
14688
|
+
return {
|
|
14689
|
+
ok: false,
|
|
14690
|
+
retryable: false,
|
|
14691
|
+
error: new HasnaHttpError(method, rel, response.status, parsed)
|
|
14692
|
+
};
|
|
14693
|
+
}
|
|
14694
|
+
if (authenticationFailure) {
|
|
14695
|
+
return {
|
|
14696
|
+
ok: false,
|
|
14697
|
+
retryable: false,
|
|
14698
|
+
error: new HasnaHttpError(method, rel, response.status, undefined, {
|
|
14699
|
+
source: credential.source,
|
|
14700
|
+
tier: credential.tier,
|
|
14701
|
+
guidance: authFailureGuidance(credential)
|
|
14702
|
+
})
|
|
14703
|
+
};
|
|
14704
|
+
}
|
|
14705
|
+
const retry = resolveRetry(opts.retry);
|
|
14706
|
+
const retryable = retry ? retry.retryStatuses.includes(response.status) : false;
|
|
14707
|
+
return { ok: false, retryable, error: new HasnaHttpError(method, rel, response.status, parsed) };
|
|
14708
|
+
}
|
|
14709
|
+
return { ok: true, value: parsed };
|
|
14710
|
+
}
|
|
14711
|
+
async function request(method, path, body, opts = {}) {
|
|
14712
|
+
const upper = method.toUpperCase();
|
|
14713
|
+
const rel = appendQuery(path.startsWith("/") ? path : `/${path}`, opts.query);
|
|
14714
|
+
const retry = resolveRetry(opts.retry);
|
|
14715
|
+
const methodRetryable = IDEMPOTENT_METHODS2.has(upper) || Boolean(opts.idempotencyKey);
|
|
14716
|
+
const maxAttempts = retry && methodRetryable ? retry.retries + 1 : 1;
|
|
14717
|
+
const binding = requestBindingProvider ? await requestBindingProvider() : {
|
|
14718
|
+
baseUrl: base,
|
|
14719
|
+
credential: await resolveRequestCredential(options.name, options.apiKey)
|
|
14720
|
+
};
|
|
14721
|
+
const url = `${binding.baseUrl}${rel}`;
|
|
14722
|
+
const credential = binding.credential;
|
|
14723
|
+
let last = null;
|
|
14724
|
+
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
14725
|
+
const result = await once(upper, rel, url, body, opts, credential);
|
|
14726
|
+
if (result.ok)
|
|
14727
|
+
return result.value;
|
|
14728
|
+
last = result;
|
|
14729
|
+
const canRetry = retry !== null && methodRetryable && result.retryable && attempt < maxAttempts;
|
|
14730
|
+
if (!canRetry)
|
|
14731
|
+
break;
|
|
14732
|
+
const backoff = Math.min(retry.maxDelayMs, retry.baseDelayMs * 2 ** (attempt - 1));
|
|
14733
|
+
const jitter = Math.floor(Math.random() * (backoff / 2 + 1));
|
|
14734
|
+
await sleep(backoff + jitter);
|
|
14735
|
+
}
|
|
14736
|
+
throw last.error;
|
|
14737
|
+
}
|
|
14738
|
+
return {
|
|
14739
|
+
baseUrl: base,
|
|
14740
|
+
request,
|
|
14741
|
+
get: (path, opts) => request("GET", path, undefined, opts),
|
|
14742
|
+
post: (path, body, opts) => request("POST", path, body, opts),
|
|
14743
|
+
put: (path, body, opts) => request("PUT", path, body, opts),
|
|
14744
|
+
patch: (path, body, opts) => request("PATCH", path, body, opts),
|
|
14745
|
+
del: (path, body, opts) => request("DELETE", path, body, opts)
|
|
14746
|
+
};
|
|
14747
|
+
}
|
|
14748
|
+
function createClientTransport(name, env = process.env, overrides) {
|
|
14749
|
+
const credentialOptions = overrides?.credentials;
|
|
14750
|
+
const snapshotOptions = { ...credentialOptions ? { credentials: credentialOptions } : {} };
|
|
14751
|
+
const resolution = resolveClientTransportSnapshot(name, env, snapshotOptions).resolution;
|
|
14752
|
+
const sameBinding = (left, right) => left.resolution.baseUrl === right.resolution.baseUrl && left.credential.apiKey === right.credential.apiKey && left.credential.pointerVaultKey === right.credential.pointerVaultKey && left.credential.source === right.credential.source && left.credential.tier === right.credential.tier;
|
|
14753
|
+
const unstableConfiguration = () => new ClientTransportConfigurationError(name, "The configured service authority or credential changed while a request was being prepared; no authenticated request was sent.");
|
|
14754
|
+
const requestBindingProvider = async () => {
|
|
14755
|
+
const first = resolveClientTransportSnapshot(name, env, snapshotOptions);
|
|
14756
|
+
const reviewed = resolveClientTransportSnapshot(name, env, snapshotOptions);
|
|
14757
|
+
if (!sameBinding(first, reviewed))
|
|
14758
|
+
throw unstableConfiguration();
|
|
14759
|
+
if (reviewed.resolution.baseUrl !== resolution.baseUrl) {
|
|
14760
|
+
throw new ClientTransportConfigurationError(name, "The configured service authority changed; rebuild the client before sending credentials.");
|
|
14761
|
+
}
|
|
14762
|
+
const credential = await resolveRequestCredential(name, () => reviewed.credential, env);
|
|
14763
|
+
const immediatelyBeforeDispatch = resolveClientTransportSnapshot(name, env, snapshotOptions);
|
|
14764
|
+
if (!sameBinding(reviewed, immediatelyBeforeDispatch))
|
|
14765
|
+
throw unstableConfiguration();
|
|
14766
|
+
if (immediatelyBeforeDispatch.resolution.baseUrl !== resolution.baseUrl) {
|
|
14767
|
+
throw new ClientTransportConfigurationError(name, "The configured service authority changed; rebuild the client before sending credentials.");
|
|
14768
|
+
}
|
|
14769
|
+
return { baseUrl: immediatelyBeforeDispatch.resolution.baseUrl, credential };
|
|
14770
|
+
};
|
|
14771
|
+
return {
|
|
14772
|
+
transport: "http",
|
|
14773
|
+
client: createHasnaHttpTransportInternal({
|
|
14774
|
+
name,
|
|
14775
|
+
baseUrl: resolution.baseUrl,
|
|
14776
|
+
apiKey: () => {
|
|
14777
|
+
throw new Error("The authenticated request binding provider was not invoked.");
|
|
14778
|
+
},
|
|
14779
|
+
...overrides?.fetchImpl ? { fetchImpl: overrides.fetchImpl } : {},
|
|
14780
|
+
...overrides?.headers ? { headers: overrides.headers } : {},
|
|
14781
|
+
...overrides?.timeoutMs ? { timeoutMs: overrides.timeoutMs } : {},
|
|
14782
|
+
...overrides?.retry !== undefined ? { retry: overrides.retry } : {},
|
|
14783
|
+
...overrides?.sleepImpl ? { sleepImpl: overrides.sleepImpl } : {}
|
|
14784
|
+
}, requestBindingProvider),
|
|
14785
|
+
resolution
|
|
14786
|
+
};
|
|
14787
|
+
}
|
|
14788
|
+
function resourcePath(resource) {
|
|
14789
|
+
const trimmed = resource.replace(/^\/+|\/+$/g, "");
|
|
14790
|
+
if (!trimmed)
|
|
14791
|
+
throw new Error("resource must be a non-empty path segment");
|
|
14792
|
+
return `/${trimmed}`;
|
|
14793
|
+
}
|
|
14794
|
+
function entityPath(resource, id) {
|
|
14795
|
+
if (id === undefined || id === null || `${id}`.length === 0) {
|
|
14796
|
+
throw new Error("id must be a non-empty string");
|
|
14797
|
+
}
|
|
14798
|
+
return `${resourcePath(resource)}/${encodeURIComponent(String(id))}`;
|
|
14799
|
+
}
|
|
14800
|
+
function newIdempotencyKey() {
|
|
14801
|
+
const g = globalThis;
|
|
14802
|
+
if (g.crypto?.randomUUID)
|
|
14803
|
+
return g.crypto.randomUUID();
|
|
14804
|
+
return `idmp_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 12)}`;
|
|
14805
|
+
}
|
|
14806
|
+
function extractItems(raw) {
|
|
14807
|
+
if (Array.isArray(raw))
|
|
14808
|
+
return raw;
|
|
14809
|
+
if (raw && typeof raw === "object") {
|
|
14810
|
+
const obj = raw;
|
|
14811
|
+
for (const key of ["items", "data", "results", "rows", "records"]) {
|
|
14812
|
+
if (Array.isArray(obj[key]))
|
|
14813
|
+
return obj[key];
|
|
14814
|
+
}
|
|
14815
|
+
}
|
|
14816
|
+
return [];
|
|
14817
|
+
}
|
|
14818
|
+
function extractTotal(raw) {
|
|
14819
|
+
if (raw && typeof raw === "object") {
|
|
14820
|
+
const obj = raw;
|
|
14821
|
+
for (const key of ["total", "count", "totalCount", "total_count"]) {
|
|
14822
|
+
if (typeof obj[key] === "number")
|
|
14823
|
+
return obj[key];
|
|
14824
|
+
}
|
|
14825
|
+
}
|
|
14826
|
+
return null;
|
|
14827
|
+
}
|
|
14828
|
+
function extractCursor(raw) {
|
|
14829
|
+
if (raw && typeof raw === "object") {
|
|
14830
|
+
const obj = raw;
|
|
14831
|
+
for (const key of ["cursor", "nextCursor", "next_cursor", "next"]) {
|
|
14832
|
+
if (typeof obj[key] === "string")
|
|
14833
|
+
return obj[key];
|
|
14834
|
+
}
|
|
14835
|
+
}
|
|
14836
|
+
return null;
|
|
14837
|
+
}
|
|
14838
|
+
function isNotFoundHttpError(error) {
|
|
14839
|
+
return typeof error === "object" && error !== null && error.name === "HasnaHttpError" && error.status === 404;
|
|
14840
|
+
}
|
|
14841
|
+
function createHasnaStorageClient(name, transport) {
|
|
14842
|
+
return {
|
|
14843
|
+
name,
|
|
14844
|
+
baseUrl: transport.baseUrl,
|
|
14845
|
+
transport,
|
|
14846
|
+
async list(resource, options = {}) {
|
|
14847
|
+
const raw = await transport.get(resourcePath(resource), options);
|
|
14848
|
+
return {
|
|
14849
|
+
items: extractItems(raw),
|
|
14850
|
+
total: extractTotal(raw),
|
|
14851
|
+
cursor: extractCursor(raw),
|
|
14852
|
+
raw
|
|
14853
|
+
};
|
|
14854
|
+
},
|
|
14855
|
+
async get(resource, id, options = {}) {
|
|
14856
|
+
try {
|
|
14857
|
+
return await transport.get(entityPath(resource, id), options);
|
|
14858
|
+
} catch (error) {
|
|
14859
|
+
if (isNotFoundHttpError(error))
|
|
14860
|
+
return null;
|
|
14861
|
+
throw error;
|
|
14862
|
+
}
|
|
14863
|
+
},
|
|
14864
|
+
async create(resource, body, options = {}) {
|
|
14865
|
+
const { idempotencyKey, ...rest } = options;
|
|
14866
|
+
return transport.post(resourcePath(resource), body, {
|
|
14867
|
+
...rest,
|
|
14868
|
+
idempotencyKey: idempotencyKey ?? newIdempotencyKey()
|
|
14869
|
+
});
|
|
14870
|
+
},
|
|
14871
|
+
async update(resource, id, patch, options = {}) {
|
|
14872
|
+
const { method = "PATCH", idempotencyKey, ...rest } = options;
|
|
14873
|
+
const call = method === "PUT" ? transport.put : transport.patch;
|
|
14874
|
+
return call(entityPath(resource, id), patch, { ...rest, ...idempotencyKey ? { idempotencyKey } : {} });
|
|
14875
|
+
},
|
|
14876
|
+
async delete(resource, id, options = {}) {
|
|
14877
|
+
try {
|
|
14878
|
+
await transport.del(entityPath(resource, id), undefined, options);
|
|
14879
|
+
} catch (error) {
|
|
14880
|
+
if (isNotFoundHttpError(error))
|
|
14881
|
+
return;
|
|
14882
|
+
throw error;
|
|
14883
|
+
}
|
|
14884
|
+
}
|
|
14885
|
+
};
|
|
14886
|
+
}
|
|
14887
|
+
function resolveStorageClient(name, env = process.env, overrides) {
|
|
14888
|
+
const wired = createClientTransport(name, env, overrides);
|
|
14889
|
+
return { transport: "http", client: createHasnaStorageClient(name, wired.client) };
|
|
14890
|
+
}
|
|
14891
|
+
var CREDENTIAL_PROFILE_ENV_KEY2 = "HASNA_PROFILE", CredentialResolutionError, CredentialFileUnsafeError, HASNA_HOME_ENV_KEY = "HASNA_HOME", HASNA_CONFIG_HOME_ENV_KEY = "HASNA_CONFIG_HOME", KEYCHAIN_STATION_ENV_KEY = "HASNA_STATION", HASNA_HOME_DIR = ".hasna", CONFIG_SUBDIR = "config", CREDENTIALS_FILE = "credentials", KEYCHAIN_SECURITY_BIN = "/usr/bin/security", KEYCHAIN_SERVICE_PREFIX = "hasna.credentials", KEYCHAIN_ITEM_NOT_FOUND_STATUS = 44, KEYCHAIN_SPAWN_TIMEOUT_MS = 1e4, MAX_CREDENTIAL_FILE_BYTES2, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, VAULT_POINTER_SHAPE, CREDENTIAL_SHAPED_KEY, INSPECT_CUSTOM2, CREDENTIAL_SEAL2, CALLER_SUPPLIED_CREDENTIAL_PROVIDER_SOURCE = "caller-supplied CredentialProvider", AMBIENT_ENVIRONMENT2, SECRETS_PACKAGE_SPECIFIER2, requireSecretsSdk2, DEFAULT_FLEET_GATEWAY_ORIGIN = "https://api.hasna.com", DEFAULT_AUTHORITY_SOURCE = "default", ASCII_CONTROL_PATTERN, DNS_LABEL_PATTERN, ClientTransportConfigurationError, HasnaHttpError, DEFAULT_RETRY_STATUSES, IDEMPOTENT_METHODS2, AUTHORITY_OVERRIDE_HEADERS2, defaultSleep = (ms) => new Promise((resolve10) => setTimeout(resolve10, ms));
|
|
14892
|
+
var init_storage = __esm(() => {
|
|
14893
|
+
CredentialResolutionError = class CredentialResolutionError extends Error {
|
|
14894
|
+
appName;
|
|
14895
|
+
attempted;
|
|
14896
|
+
constructor(appName, message, attempted) {
|
|
14897
|
+
super(message);
|
|
14898
|
+
this.name = "CredentialResolutionError";
|
|
14899
|
+
this.appName = appName;
|
|
14900
|
+
this.attempted = attempted;
|
|
14901
|
+
}
|
|
14902
|
+
};
|
|
14903
|
+
CredentialFileUnsafeError = class CredentialFileUnsafeError extends Error {
|
|
14904
|
+
path;
|
|
14905
|
+
constructor(path, reason) {
|
|
14906
|
+
super(`Refusing unsafe credential/config file ${path}: ${reason}.`);
|
|
14907
|
+
this.name = "CredentialFileUnsafeError";
|
|
14908
|
+
this.path = path;
|
|
14909
|
+
}
|
|
14910
|
+
};
|
|
14911
|
+
MAX_CREDENTIAL_FILE_BYTES2 = 64 * 1024;
|
|
14912
|
+
SAFE_APP_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
14913
|
+
SAFE_PROFILE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
|
|
14914
|
+
ILLEGAL_IN_HEADER_VALUE = /[^\t\x20-\x7e]/;
|
|
14915
|
+
VAULT_POINTER_SHAPE = /^[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-_.]*){2,}$/;
|
|
14916
|
+
CREDENTIAL_SHAPED_KEY = /(?:^|_)(?:API_KEY|KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)(?:_|$)/;
|
|
14917
|
+
INSPECT_CUSTOM2 = Symbol.for("nodejs.util.inspect.custom");
|
|
14918
|
+
CREDENTIAL_SEAL2 = Symbol.for("hasna:contracts:sealedCredential");
|
|
14919
|
+
AMBIENT_ENVIRONMENT2 = Symbol.for("hasna:contracts:ambientClientEnvironment");
|
|
14920
|
+
SECRETS_PACKAGE_SPECIFIER2 = "@hasna/" + "secrets";
|
|
14921
|
+
requireSecretsSdk2 = createRequire2(import.meta.url);
|
|
14922
|
+
ASCII_CONTROL_PATTERN = /[\u0000-\u001f\u007f]/;
|
|
14923
|
+
DNS_LABEL_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
14924
|
+
ClientTransportConfigurationError = class ClientTransportConfigurationError extends Error {
|
|
14925
|
+
appName;
|
|
14926
|
+
sources;
|
|
14927
|
+
constructor(appName, message, sources = []) {
|
|
14928
|
+
super(message);
|
|
14929
|
+
this.name = "ClientTransportConfigurationError";
|
|
14930
|
+
this.appName = appName;
|
|
14931
|
+
this.sources = Object.freeze([...sources]);
|
|
14932
|
+
}
|
|
14933
|
+
};
|
|
14934
|
+
HasnaHttpError = class HasnaHttpError extends Error {
|
|
14935
|
+
status;
|
|
14936
|
+
method;
|
|
14937
|
+
path;
|
|
14938
|
+
credentialSource;
|
|
14939
|
+
credentialTier;
|
|
14940
|
+
constructor(method, path, status, body, credential) {
|
|
14941
|
+
const guidance = credential ? `. ${credential.guidance}` : "";
|
|
14942
|
+
super(`Hasna cloud request failed: ${method} ${path} -> ${status}${guidance}`);
|
|
14943
|
+
this.name = "HasnaHttpError";
|
|
14944
|
+
this.status = status;
|
|
14945
|
+
this.method = method;
|
|
14946
|
+
this.path = path;
|
|
14947
|
+
Object.defineProperty(this, "body", {
|
|
14948
|
+
value: body,
|
|
14949
|
+
enumerable: status !== 401 && status !== 403,
|
|
14950
|
+
writable: false,
|
|
14951
|
+
configurable: false
|
|
14952
|
+
});
|
|
14953
|
+
this.credentialSource = credential?.source ?? null;
|
|
14954
|
+
this.credentialTier = credential?.tier ?? null;
|
|
14955
|
+
}
|
|
14956
|
+
};
|
|
14957
|
+
DEFAULT_RETRY_STATUSES = [408, 425, 429, 500, 502, 503, 504];
|
|
14958
|
+
IDEMPOTENT_METHODS2 = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
14959
|
+
AUTHORITY_OVERRIDE_HEADERS2 = new Set([
|
|
14960
|
+
"host",
|
|
14961
|
+
":authority",
|
|
14962
|
+
"forwarded",
|
|
14963
|
+
"x-forwarded-host",
|
|
14964
|
+
"x-original-host"
|
|
14965
|
+
]);
|
|
14966
|
+
});
|
|
14967
|
+
|
|
14968
|
+
// src/lib/transport-resolver.ts
|
|
14969
|
+
function rethrowInstructionsAuthorityFailure(error) {
|
|
14970
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
14971
|
+
const name = error instanceof Error ? error.name : "";
|
|
14972
|
+
const failure = (code, lead) => {
|
|
14973
|
+
throw new Error(`${code}: ${lead} ${message} There is no local fallback: local SQLite is opt-in only ` + `(${INSTRUCTIONS_LOCAL_OPT_IN_ENV}=1) and is disabled by default \u2014 failing closed`, { cause: error });
|
|
14974
|
+
};
|
|
14975
|
+
if (name === "CredentialResolutionError" || name === "CredentialFileUnsafeError") {
|
|
14976
|
+
return failure("REMOTE_API_CREDENTIAL_INVALID", "The configured Instructions credential could not be used.");
|
|
14977
|
+
}
|
|
14978
|
+
if (/no API key could be resolved/.test(message)) {
|
|
14979
|
+
if (/is not set and no API key could be resolved/.test(message)) {
|
|
14980
|
+
return failure("REMOTE_API_CONFIG_MISSING", "no Instructions credential resolved from the Keychain item " + "hasna.credentials.instructions.api-key, ~/.hasna/instructions/config/credentials, " + `or ${clientTransportEnvKeys(INSTRUCTIONS_APP).apiKeyKeys[0]}.`);
|
|
14981
|
+
}
|
|
14982
|
+
return failure("REMOTE_API_KEY_MISSING", "an Instructions authority is configured but no API key resolved \u2014 looked in " + "hasna.credentials.instructions.api-key, ~/.hasna/instructions/config/credentials, " + `and ${clientTransportEnvKeys(INSTRUCTIONS_APP).apiKeyKeys[0]}.`);
|
|
14983
|
+
}
|
|
14984
|
+
return failure("REMOTE_API_URL_INVALID", "the configured Instructions authority is invalid.");
|
|
14985
|
+
}
|
|
14986
|
+
function resolveInstructionsStorageClient(env = process.env, options = {}) {
|
|
14987
|
+
const inputs = instructionsResolverInputs(env, options.credentials);
|
|
14988
|
+
try {
|
|
14989
|
+
return resolveStorageClient(INSTRUCTIONS_APP, inputs.env, {
|
|
14990
|
+
fetchImpl: (input, init) => fetch(input, { ...init, redirect: "manual" }),
|
|
14991
|
+
credentials: inputs.credentials
|
|
14992
|
+
});
|
|
14993
|
+
} catch (error) {
|
|
14994
|
+
rethrowInstructionsAuthorityFailure(error);
|
|
14995
|
+
}
|
|
14996
|
+
}
|
|
14997
|
+
function announceLocalInstructionsMode(write = (line) => process.stderr.write(`${line}
|
|
14998
|
+
`)) {
|
|
14999
|
+
if (localNoticePrinted)
|
|
15000
|
+
return false;
|
|
15001
|
+
localNoticePrinted = true;
|
|
15002
|
+
write(instructionsLocalModeNotice());
|
|
15003
|
+
return true;
|
|
15004
|
+
}
|
|
15005
|
+
var INSTRUCTIONS_APP = "instructions", INSTRUCTIONS_LOCAL_OPT_IN_ENV, localNoticePrinted = false;
|
|
15006
|
+
var init_transport_resolver = __esm(() => {
|
|
15007
|
+
init_transport();
|
|
15008
|
+
init_storage();
|
|
15009
|
+
init_local_opt_in();
|
|
15010
|
+
INSTRUCTIONS_LOCAL_OPT_IN_ENV = INSTRUCTIONS_LOCAL_OPT_IN_ENV_KEYS[0];
|
|
15011
|
+
});
|
|
15012
|
+
|
|
13685
15013
|
// src/data/config-store.ts
|
|
13686
15014
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
13687
15015
|
function parseBoundedPagePayload(value, label) {
|
|
@@ -13689,7 +15017,7 @@ function parseBoundedPagePayload(value, label) {
|
|
|
13689
15017
|
const consumed = Number(page?.cursor) + (page?.items?.length ?? 0);
|
|
13690
15018
|
const complete = Boolean(page && Number.isSafeInteger(page.total) && consumed >= Number(page.total));
|
|
13691
15019
|
if (!page || !Array.isArray(page.items) || !Number.isSafeInteger(page.total) || Number(page.total) < 0 || !Number.isSafeInteger(page.limit) || Number(page.limit) < 1 || !Number.isSafeInteger(page.cursor) || Number(page.cursor) < 0 || page.items.length > Number(page.limit) || typeof page.has_more !== "boolean" || typeof page.complete !== "boolean" || page.truncated !== false || page.next_cursor !== null && !Number.isSafeInteger(page.next_cursor) || page.complete !== complete || page.has_more !== !complete || page.next_cursor !== (complete ? null : consumed)) {
|
|
13692
|
-
throw new
|
|
15020
|
+
throw new Error(`${label} returned an invalid or truncated bounded-read envelope`);
|
|
13693
15021
|
}
|
|
13694
15022
|
return {
|
|
13695
15023
|
...page,
|
|
@@ -13704,50 +15032,38 @@ function parseBoundedOrLegacyPage(value, legacyItems, options, label) {
|
|
|
13704
15032
|
}
|
|
13705
15033
|
}
|
|
13706
15034
|
if (!Array.isArray(legacyItems)) {
|
|
13707
|
-
throw new
|
|
15035
|
+
throw new Error(`${label} returned neither a bounded envelope nor a complete legacy array`);
|
|
13708
15036
|
}
|
|
13709
15037
|
const normalized = normalizeBoundedReadOptions(options);
|
|
13710
15038
|
const page = boundedReadPage(legacyItems.slice(normalized.cursor, normalized.cursor + normalized.limit), legacyItems.length, normalized);
|
|
13711
15039
|
return { ...page, source_bounded: false };
|
|
13712
15040
|
}
|
|
13713
15041
|
function isCloudAuthError(err) {
|
|
13714
|
-
return err
|
|
15042
|
+
return typeof err === "object" && err !== null && err.name === "HasnaHttpError" && (err.status === 401 || err.status === 403);
|
|
15043
|
+
}
|
|
15044
|
+
function isNotFoundHttpError2(err) {
|
|
15045
|
+
return typeof err === "object" && err !== null && err.name === "HasnaHttpError" && err.status === 404;
|
|
13715
15046
|
}
|
|
13716
15047
|
function formatCliError(err, env = process.env) {
|
|
13717
15048
|
if (isCloudAuthError(err)) {
|
|
13718
|
-
const apiUrl = env[API_URL_ENV]?.trim();
|
|
13719
|
-
const detail = err.message?.trim();
|
|
13720
|
-
const serverNote = detail && !/^HTTP \d+\b/.test(detail) ? ` Server said: ${detail}` : "";
|
|
13721
15049
|
return [
|
|
13722
15050
|
`Instructions cloud API rejected the request (HTTP ${err.status}: authentication failed).`,
|
|
13723
|
-
|
|
13724
|
-
` The API key in ${API_KEY_ENV} is missing, expired, or revoked${apiUrl ? ` for ${apiUrl}` : ""}.`,
|
|
15051
|
+
` The API key in use is missing, expired, or revoked (the transport never echoes the server's 401/403 body).`,
|
|
13725
15052
|
` To continue, either:`,
|
|
13726
|
-
` - set a valid key: export
|
|
13727
|
-
`
|
|
13728
|
-
|
|
15053
|
+
` - set a valid key: export HASNA_INSTRUCTIONS_API_KEY=<new-key>`,
|
|
15054
|
+
` (or add the Keychain item hasna.credentials.instructions.api-key, or write`,
|
|
15055
|
+
` ~/.hasna/instructions/config/credentials)`,
|
|
15056
|
+
` - or opt in to the local store explicitly: export ${LOCAL_OPT_IN_ENV}=1`
|
|
15057
|
+
].join(`
|
|
13729
15058
|
`);
|
|
13730
15059
|
}
|
|
13731
15060
|
return err instanceof Error ? err.message : String(err);
|
|
13732
15061
|
}
|
|
13733
|
-
function resolveCloudConfig(env = process.env) {
|
|
13734
|
-
assertNoLegacyStorageMode(env);
|
|
13735
|
-
const apiUrl = env[API_URL_ENV]?.trim();
|
|
13736
|
-
const apiKey = env[API_KEY_ENV]?.trim();
|
|
13737
|
-
if (!apiUrl && !apiKey)
|
|
13738
|
-
return null;
|
|
13739
|
-
if (!apiUrl || !apiKey) {
|
|
13740
|
-
throw new Error(`API mode requires BOTH ${API_URL_ENV} and ${API_KEY_ENV}; only ` + `${apiUrl ? API_URL_ENV : API_KEY_ENV} is set. Set both to use the HTTP API, ` + `or unset both to use the local store.`);
|
|
13741
|
-
}
|
|
13742
|
-
return { apiUrl, apiKey };
|
|
13743
|
-
}
|
|
13744
|
-
function isApiTransport(env = process.env) {
|
|
13745
|
-
return resolveCloudConfig(env) !== null;
|
|
13746
|
-
}
|
|
13747
15062
|
|
|
13748
15063
|
class LocalConfigStore {
|
|
13749
15064
|
db;
|
|
13750
15065
|
mode = "local";
|
|
15066
|
+
v1BaseUrl = null;
|
|
13751
15067
|
constructor(db) {
|
|
13752
15068
|
this.db = db;
|
|
13753
15069
|
}
|
|
@@ -13876,50 +15192,22 @@ class LocalConfigStore {
|
|
|
13876
15192
|
|
|
13877
15193
|
class CloudConfigStore {
|
|
13878
15194
|
mode = "api";
|
|
13879
|
-
|
|
13880
|
-
|
|
13881
|
-
|
|
13882
|
-
|
|
13883
|
-
this.
|
|
13884
|
-
this.apiKey = config.apiKey;
|
|
13885
|
-
this.timeoutMs = config.timeoutMs ?? 30000;
|
|
15195
|
+
v1BaseUrl;
|
|
15196
|
+
client;
|
|
15197
|
+
constructor(client) {
|
|
15198
|
+
this.client = client;
|
|
15199
|
+
this.v1BaseUrl = client.baseUrl;
|
|
13886
15200
|
}
|
|
13887
15201
|
async request(method, path, body, opts = {}) {
|
|
13888
|
-
const controller = new AbortController;
|
|
13889
|
-
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
13890
|
-
const headers = {
|
|
13891
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
13892
|
-
Accept: "application/json"
|
|
13893
|
-
};
|
|
13894
|
-
if (body !== undefined)
|
|
13895
|
-
headers["Content-Type"] = "application/json";
|
|
13896
|
-
if (opts.idempotent)
|
|
13897
|
-
headers["Idempotency-Key"] = randomUUID6();
|
|
13898
15202
|
try {
|
|
13899
|
-
const
|
|
13900
|
-
|
|
13901
|
-
headers,
|
|
13902
|
-
body: body === undefined ? undefined : JSON.stringify(body),
|
|
13903
|
-
signal: controller.signal
|
|
15203
|
+
const data = await this.client.transport.request(method, path, body, {
|
|
15204
|
+
...opts.idempotent ? { idempotencyKey: randomUUID6() } : {}
|
|
13904
15205
|
});
|
|
13905
|
-
|
|
15206
|
+
return { status: 200, data };
|
|
15207
|
+
} catch (err) {
|
|
15208
|
+
if (opts.allow404 && isNotFoundHttpError2(err))
|
|
13906
15209
|
return { status: 404, data: null };
|
|
13907
|
-
|
|
13908
|
-
let parsed = null;
|
|
13909
|
-
if (text) {
|
|
13910
|
-
try {
|
|
13911
|
-
parsed = JSON.parse(text);
|
|
13912
|
-
} catch {
|
|
13913
|
-
parsed = text;
|
|
13914
|
-
}
|
|
13915
|
-
}
|
|
13916
|
-
if (!res.ok) {
|
|
13917
|
-
const message = (parsed && typeof parsed === "object" && "error" in parsed ? String(parsed.error) : undefined) ?? `HTTP ${res.status} on ${method} ${path}`;
|
|
13918
|
-
throw new CloudHttpError(res.status, message, parsed);
|
|
13919
|
-
}
|
|
13920
|
-
return { status: res.status, data: parsed };
|
|
13921
|
-
} finally {
|
|
13922
|
-
clearTimeout(timer);
|
|
15210
|
+
throw err;
|
|
13923
15211
|
}
|
|
13924
15212
|
}
|
|
13925
15213
|
async listConfigs(filter = {}) {
|
|
@@ -14075,7 +15363,7 @@ class CloudConfigStore {
|
|
|
14075
15363
|
if (first.status !== 404) {
|
|
14076
15364
|
if (isUsable(first.data))
|
|
14077
15365
|
return first;
|
|
14078
|
-
throw new
|
|
15366
|
+
throw new Error(`Cloud /v1 returned an invalid profile response for ${pathForId(idOrSlug)}`);
|
|
14079
15367
|
}
|
|
14080
15368
|
const profiles = await this.listProfiles();
|
|
14081
15369
|
const profile = profiles.find((candidate) => candidate.id === idOrSlug || candidate.slug === idOrSlug);
|
|
@@ -14088,7 +15376,7 @@ class CloudConfigStore {
|
|
|
14088
15376
|
if (response.status !== 404) {
|
|
14089
15377
|
if (isUsable(response.data))
|
|
14090
15378
|
return response;
|
|
14091
|
-
throw new
|
|
15379
|
+
throw new Error(`Cloud /v1 returned an invalid profile response for ${pathForId(candidate)}`);
|
|
14092
15380
|
}
|
|
14093
15381
|
}
|
|
14094
15382
|
return { status: 404, data: null };
|
|
@@ -14171,7 +15459,7 @@ class CloudConfigStore {
|
|
|
14171
15459
|
}
|
|
14172
15460
|
if (data && "complete" in data) {
|
|
14173
15461
|
if (data.complete !== true || data.truncated !== false) {
|
|
14174
|
-
throw new
|
|
15462
|
+
throw new Error("Cloud /v1 profile resolve returned an incomplete or truncated read");
|
|
14175
15463
|
}
|
|
14176
15464
|
return { ...data, source_bounded: data.source_bounded ?? true };
|
|
14177
15465
|
}
|
|
@@ -14187,7 +15475,7 @@ class CloudConfigStore {
|
|
|
14187
15475
|
};
|
|
14188
15476
|
}
|
|
14189
15477
|
{
|
|
14190
|
-
throw new
|
|
15478
|
+
throw new Error("Cloud /v1 profile resolve returned an incomplete or truncated read");
|
|
14191
15479
|
}
|
|
14192
15480
|
}
|
|
14193
15481
|
async registerMachine(hostname2, os, arch2) {
|
|
@@ -14210,14 +15498,19 @@ class CloudConfigStore {
|
|
|
14210
15498
|
});
|
|
14211
15499
|
}
|
|
14212
15500
|
async reset() {
|
|
14213
|
-
throw new Error("`init --force` cannot wipe the shared cloud store from a client. " + "
|
|
15501
|
+
throw new Error("`init --force` cannot wipe the shared cloud store from a client. " + "Point this run at the local store (HASNA_INSTRUCTIONS_LOCAL=1 with no hosted credential) to reset it instead.");
|
|
14214
15502
|
}
|
|
14215
15503
|
}
|
|
14216
|
-
function resolveConfigStore(env = process.env) {
|
|
14217
|
-
|
|
14218
|
-
|
|
15504
|
+
function resolveConfigStore(env = process.env, options = {}) {
|
|
15505
|
+
if (selectsInstructionsLocalStore(env)) {
|
|
15506
|
+
if (env === process.env)
|
|
15507
|
+
announceLocalInstructionsMode();
|
|
15508
|
+
return new LocalConfigStore;
|
|
15509
|
+
}
|
|
15510
|
+
const { client } = resolveInstructionsStorageClient(env, options);
|
|
15511
|
+
return new CloudConfigStore(client);
|
|
14219
15512
|
}
|
|
14220
|
-
var
|
|
15513
|
+
var LOCAL_OPT_IN_ENV;
|
|
14221
15514
|
var init_config_store = __esm(() => {
|
|
14222
15515
|
init_configs();
|
|
14223
15516
|
init_profiles();
|
|
@@ -14227,22 +15520,14 @@ var init_config_store = __esm(() => {
|
|
|
14227
15520
|
init_types();
|
|
14228
15521
|
init_bounded_read();
|
|
14229
15522
|
init_instruction_graph();
|
|
14230
|
-
|
|
14231
|
-
|
|
14232
|
-
|
|
14233
|
-
body;
|
|
14234
|
-
constructor(status, message, body) {
|
|
14235
|
-
super(message);
|
|
14236
|
-
this.status = status;
|
|
14237
|
-
this.body = body;
|
|
14238
|
-
this.name = "CloudHttpError";
|
|
14239
|
-
}
|
|
14240
|
-
};
|
|
15523
|
+
init_transport_resolver();
|
|
15524
|
+
init_local_opt_in();
|
|
15525
|
+
LOCAL_OPT_IN_ENV = INSTRUCTIONS_LOCAL_OPT_IN_ENV;
|
|
14241
15526
|
});
|
|
14242
15527
|
|
|
14243
15528
|
// src/lib/session-render-ownership.ts
|
|
14244
|
-
import { existsSync as existsSync11, readFileSync as
|
|
14245
|
-
import { dirname as dirname5, join as
|
|
15529
|
+
import { existsSync as existsSync11, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
15530
|
+
import { dirname as dirname5, join as join13, parse as parse3, relative as relative3, sep } from "path";
|
|
14246
15531
|
function toSegments(absolutePath2) {
|
|
14247
15532
|
return absolutePath2.replaceAll("\\", "/").split("/").filter(Boolean);
|
|
14248
15533
|
}
|
|
@@ -14273,7 +15558,7 @@ function readManifestRelativePaths(manifestPath) {
|
|
|
14273
15558
|
}
|
|
14274
15559
|
let manifest;
|
|
14275
15560
|
try {
|
|
14276
|
-
manifest = JSON.parse(
|
|
15561
|
+
manifest = JSON.parse(readFileSync6(manifestPath, "utf-8"));
|
|
14277
15562
|
} catch {
|
|
14278
15563
|
return null;
|
|
14279
15564
|
}
|
|
@@ -14290,7 +15575,7 @@ function sessionRenderManifestClaimsPath(absolutePath2) {
|
|
|
14290
15575
|
const root = parse3(absolutePath2).root;
|
|
14291
15576
|
let home = dirname5(absolutePath2);
|
|
14292
15577
|
for (let depth = 0;depth < MANIFEST_ANCESTOR_LIMIT; depth += 1) {
|
|
14293
|
-
const manifestPath =
|
|
15578
|
+
const manifestPath = join13(home, ...SESSION_RENDER_MANIFEST_RELATIVE_PATH.split("/"));
|
|
14294
15579
|
const relativePaths = readManifestRelativePaths(manifestPath);
|
|
14295
15580
|
if (relativePaths) {
|
|
14296
15581
|
const claimed = relative3(home, absolutePath2).split(sep).join("/");
|
|
@@ -14325,11 +15610,11 @@ __export(exports_apply, {
|
|
|
14325
15610
|
applyConfigs: () => applyConfigs,
|
|
14326
15611
|
applyConfig: () => applyConfig
|
|
14327
15612
|
});
|
|
14328
|
-
import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as
|
|
14329
|
-
import { basename as basename5, dirname as dirname6, join as
|
|
14330
|
-
import { homedir as
|
|
15613
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync7, realpathSync as realpathSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
15614
|
+
import { basename as basename5, dirname as dirname6, join as join14, resolve as resolve10 } from "path";
|
|
15615
|
+
import { homedir as homedir8 } from "os";
|
|
14331
15616
|
function getConfigHome() {
|
|
14332
|
-
return process.env["CONFIGS_HOME"] || process.env["HOME"] ||
|
|
15617
|
+
return process.env["CONFIGS_HOME"] || process.env["HOME"] || homedir8();
|
|
14333
15618
|
}
|
|
14334
15619
|
function expandPath(p) {
|
|
14335
15620
|
if (p.startsWith("~/")) {
|
|
@@ -14376,7 +15661,7 @@ async function writeConfigResult(config, targetPath, content, opts, meta = {}) {
|
|
|
14376
15661
|
}
|
|
14377
15662
|
const path = expandPath(renderedTargetPath);
|
|
14378
15663
|
const renderedForTarget = isCursorGlobalAuthorityPath(path) ? stampCursorGlobalAuthorityMarker(renderedContent) : renderedContent;
|
|
14379
|
-
const previousContent = existsSync12(path) ?
|
|
15664
|
+
const previousContent = existsSync12(path) ? readFileSync7(path, "utf-8") : null;
|
|
14380
15665
|
const changed = previousContent !== renderedForTarget;
|
|
14381
15666
|
if (!opts.dryRun) {
|
|
14382
15667
|
const dir = dirname6(path);
|
|
@@ -14416,7 +15701,7 @@ function wouldDestroyACredential(targetPath, renderedContent, format) {
|
|
|
14416
15701
|
const path = expandPath(targetPath);
|
|
14417
15702
|
if (!existsSync12(path))
|
|
14418
15703
|
return [];
|
|
14419
|
-
current =
|
|
15704
|
+
current = readFileSync7(path, "utf-8");
|
|
14420
15705
|
} catch {
|
|
14421
15706
|
return secretTokens;
|
|
14422
15707
|
}
|
|
@@ -14740,7 +16025,7 @@ function sessionRendererOwnsCanonicalTarget(normalized, opts) {
|
|
|
14740
16025
|
getConfigHome(),
|
|
14741
16026
|
opts.vars?.["HOME_DIR"]
|
|
14742
16027
|
].filter((home) => typeof home === "string" && home.length > 0));
|
|
14743
|
-
if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(
|
|
16028
|
+
if ([...homes].some((home) => SESSION_RENDER_OWNED_CONFIG_TARGETS.some((relativePath) => normalized === normalizeTargetPath(join14(home, ...relativePath.split("/"))))))
|
|
14744
16029
|
return true;
|
|
14745
16030
|
return sessionRenderOwnsPath(normalized);
|
|
14746
16031
|
}
|
|
@@ -14758,9 +16043,9 @@ var init_apply = __esm(() => {
|
|
|
14758
16043
|
});
|
|
14759
16044
|
|
|
14760
16045
|
// src/lib/sync-dir.ts
|
|
14761
|
-
import { existsSync as existsSync13, readdirSync as readdirSync2, readFileSync as
|
|
14762
|
-
import { join as
|
|
14763
|
-
import { homedir as
|
|
16046
|
+
import { existsSync as existsSync13, readdirSync as readdirSync2, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
|
|
16047
|
+
import { join as join15, relative as relative4 } from "path";
|
|
16048
|
+
import { homedir as homedir9 } from "os";
|
|
14764
16049
|
function shouldSkip(p) {
|
|
14765
16050
|
return SKIP.some((s) => p.includes(s));
|
|
14766
16051
|
}
|
|
@@ -14769,9 +16054,9 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
14769
16054
|
const absDir = expandPath(dir);
|
|
14770
16055
|
if (!existsSync13(absDir))
|
|
14771
16056
|
return { added: 0, updated: 0, unchanged: 0, skipped: [`Not found: ${absDir}`] };
|
|
14772
|
-
const files = opts.recursive !== false ? walkDir(absDir) : readdirSync2(absDir).map((f) =>
|
|
16057
|
+
const files = opts.recursive !== false ? walkDir(absDir) : readdirSync2(absDir).map((f) => join15(absDir, f)).filter((f) => statSync5(f).isFile());
|
|
14773
16058
|
const result = { added: 0, updated: 0, unchanged: 0, skipped: [] };
|
|
14774
|
-
const home =
|
|
16059
|
+
const home = homedir9();
|
|
14775
16060
|
const allConfigs = await store.listConfigs();
|
|
14776
16061
|
for (const file of files) {
|
|
14777
16062
|
if (shouldSkip(file)) {
|
|
@@ -14779,7 +16064,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
14779
16064
|
continue;
|
|
14780
16065
|
}
|
|
14781
16066
|
try {
|
|
14782
|
-
const content =
|
|
16067
|
+
const content = readFileSync8(file, "utf-8");
|
|
14783
16068
|
if (content.length > 500000) {
|
|
14784
16069
|
result.skipped.push(file + " (too large)");
|
|
14785
16070
|
continue;
|
|
@@ -14806,7 +16091,7 @@ async function syncFromDir(dir, opts = {}) {
|
|
|
14806
16091
|
}
|
|
14807
16092
|
async function syncToDir(dir, opts = {}) {
|
|
14808
16093
|
const store = opts.store ?? resolveConfigStore();
|
|
14809
|
-
const home =
|
|
16094
|
+
const home = homedir9();
|
|
14810
16095
|
const absDir = expandPath(dir);
|
|
14811
16096
|
const normalized = dir.startsWith("~/") ? dir : absDir.replace(home, "~");
|
|
14812
16097
|
const configs = (await store.listConfigs()).filter((c) => c.target_path && (c.target_path.startsWith(normalized) || c.target_path.startsWith(absDir)));
|
|
@@ -14830,7 +16115,7 @@ async function syncToDir(dir, opts = {}) {
|
|
|
14830
16115
|
}
|
|
14831
16116
|
function walkDir(dir, files = []) {
|
|
14832
16117
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
14833
|
-
const full =
|
|
16118
|
+
const full = join15(dir, entry.name);
|
|
14834
16119
|
if (shouldSkip(full))
|
|
14835
16120
|
continue;
|
|
14836
16121
|
if (entry.isDirectory())
|
|
@@ -14865,8 +16150,8 @@ __export(exports_sync, {
|
|
|
14865
16150
|
KNOWN_CONFIGS: () => KNOWN_CONFIGS,
|
|
14866
16151
|
CLAUDE_PROMPT_OUTPUTS: () => CLAUDE_PROMPT_OUTPUTS
|
|
14867
16152
|
});
|
|
14868
|
-
import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as
|
|
14869
|
-
import { basename as basename6, extname as extname3, join as
|
|
16153
|
+
import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
|
|
16154
|
+
import { basename as basename6, extname as extname3, join as join16 } from "path";
|
|
14870
16155
|
function claudeRuleOutputs(fileName) {
|
|
14871
16156
|
const stem = basename6(fileName, extname3(fileName));
|
|
14872
16157
|
return [
|
|
@@ -14930,11 +16215,11 @@ async function syncProject(opts) {
|
|
|
14930
16215
|
const allConfigs = await store.listConfigs();
|
|
14931
16216
|
const machine = detectMachineContext();
|
|
14932
16217
|
for (const pf of PROJECT_CONFIG_FILES) {
|
|
14933
|
-
const abs =
|
|
16218
|
+
const abs = join16(absDir, pf.file);
|
|
14934
16219
|
if (!existsSync14(abs))
|
|
14935
16220
|
continue;
|
|
14936
16221
|
try {
|
|
14937
|
-
const rawContent =
|
|
16222
|
+
const rawContent = readFileSync9(abs, "utf-8");
|
|
14938
16223
|
if (rawContent.length > 500000) {
|
|
14939
16224
|
result.skipped.push(pf.file);
|
|
14940
16225
|
continue;
|
|
@@ -14963,20 +16248,20 @@ async function syncProject(opts) {
|
|
|
14963
16248
|
}
|
|
14964
16249
|
}
|
|
14965
16250
|
for (const ruleDir of [
|
|
14966
|
-
{ dir:
|
|
14967
|
-
{ dir:
|
|
14968
|
-
{ dir:
|
|
14969
|
-
{ dir:
|
|
14970
|
-
{ dir:
|
|
14971
|
-
{ dir:
|
|
14972
|
-
{ dir:
|
|
16251
|
+
{ dir: join16(absDir, ".claude", "rules"), agent: "claude", namePrefix: "rules" },
|
|
16252
|
+
{ dir: join16(absDir, ".agents", "rules"), agent: "antigravity", namePrefix: "antigravity-rules" },
|
|
16253
|
+
{ dir: join16(absDir, ".cursor", "rules"), agent: "cursor", namePrefix: "cursor-rules" },
|
|
16254
|
+
{ dir: join16(absDir, ".github", "instructions"), agent: "copilot", namePrefix: "copilot-instructions" },
|
|
16255
|
+
{ dir: join16(absDir, ".devin", "rules"), agent: "devin", namePrefix: "devin-rules" },
|
|
16256
|
+
{ dir: join16(absDir, ".windsurf", "rules"), agent: "windsurf-legacy", namePrefix: "windsurf-rules" },
|
|
16257
|
+
{ dir: join16(absDir, ".clinerules"), agent: "cline", namePrefix: "cline-rules" }
|
|
14973
16258
|
]) {
|
|
14974
16259
|
if (!existsSync14(ruleDir.dir))
|
|
14975
16260
|
continue;
|
|
14976
16261
|
const mdFiles = readdirSync3(ruleDir.dir).filter((f) => f.endsWith(".md") || f.endsWith(".mdc"));
|
|
14977
16262
|
for (const f of mdFiles) {
|
|
14978
|
-
const abs =
|
|
14979
|
-
const raw =
|
|
16263
|
+
const abs = join16(ruleDir.dir, f);
|
|
16264
|
+
const raw = readFileSync9(abs, "utf-8");
|
|
14980
16265
|
const redacted = redactContent(raw, "markdown");
|
|
14981
16266
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
14982
16267
|
const content = machineAware.content;
|
|
@@ -15022,13 +16307,13 @@ async function syncKnown(opts = {}) {
|
|
|
15022
16307
|
const extensions = known.rulesExtensions ?? [".md", ".mdc"];
|
|
15023
16308
|
const ruleFiles = readdirSync3(absDir).filter((f) => extensions.some((ext) => f.endsWith(ext)));
|
|
15024
16309
|
for (const f of ruleFiles) {
|
|
15025
|
-
const abs2 =
|
|
16310
|
+
const abs2 = join16(absDir, f);
|
|
15026
16311
|
const targetPath = abs2.replace(home, "~");
|
|
15027
16312
|
if (existingOutputOwners.has(normalizeTargetPath(targetPath)) || isKnownGeneratedTargetPath(targetPath)) {
|
|
15028
16313
|
result.skipped.push(`${targetPath} (generated output)`);
|
|
15029
16314
|
continue;
|
|
15030
16315
|
}
|
|
15031
|
-
const raw =
|
|
16316
|
+
const raw = readFileSync9(abs2, "utf-8");
|
|
15032
16317
|
const redacted = redactContent(raw, "markdown");
|
|
15033
16318
|
const machineAware = templateizeMachineContent(redacted.content, machine);
|
|
15034
16319
|
const content = machineAware.content;
|
|
@@ -15061,7 +16346,7 @@ async function syncKnown(opts = {}) {
|
|
|
15061
16346
|
continue;
|
|
15062
16347
|
}
|
|
15063
16348
|
try {
|
|
15064
|
-
const rawContent = normalizeKnownConfigSource(known,
|
|
16349
|
+
const rawContent = normalizeKnownConfigSource(known, readFileSync9(abs, "utf-8"));
|
|
15065
16350
|
if (rawContent.length > 500000) {
|
|
15066
16351
|
result.skipped.push(known.path + " (too large)");
|
|
15067
16352
|
continue;
|
|
@@ -15164,7 +16449,7 @@ function buildDiff(expectedContent, targetPath, storedFormat, showSecrets) {
|
|
|
15164
16449
|
const path = expandPath(targetPath);
|
|
15165
16450
|
if (!existsSync14(path))
|
|
15166
16451
|
return `(file not found on disk: ${path})`;
|
|
15167
|
-
const diskContent =
|
|
16452
|
+
const diskContent = readFileSync9(path, "utf-8");
|
|
15168
16453
|
if (diskContent === expectedContent)
|
|
15169
16454
|
return "(no diff \u2014 identical)";
|
|
15170
16455
|
const format = redactFormatForTarget(targetPath, storedFormat);
|
|
@@ -15397,9 +16682,9 @@ __export(exports_package_manager_guard, {
|
|
|
15397
16682
|
scanPackageManagerSecrets: () => scanPackageManagerSecrets
|
|
15398
16683
|
});
|
|
15399
16684
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
15400
|
-
import { existsSync as existsSync23, lstatSync as lstatSync7, readdirSync as readdirSync6, readFileSync as
|
|
15401
|
-
import { homedir as
|
|
15402
|
-
import { basename as basename7, dirname as dirname11, isAbsolute as
|
|
16685
|
+
import { existsSync as existsSync23, lstatSync as lstatSync7, readdirSync as readdirSync6, readFileSync as readFileSync17 } from "fs";
|
|
16686
|
+
import { homedir as homedir12 } from "os";
|
|
16687
|
+
import { basename as basename7, dirname as dirname11, isAbsolute as isAbsolute6, join as join24, relative as relative7, resolve as resolve15 } from "path";
|
|
15403
16688
|
function scanPackageManagerSecrets(options = {}) {
|
|
15404
16689
|
const cwd = options.cwd ? resolve15(options.cwd) : process.cwd();
|
|
15405
16690
|
const roots = (options.roots && options.roots.length > 0 ? options.roots : [cwd]).map((root) => resolve15(cwd, root));
|
|
@@ -15433,9 +16718,9 @@ function scanPackageManagerSecrets(options = {}) {
|
|
|
15433
16718
|
}
|
|
15434
16719
|
}
|
|
15435
16720
|
if (options.includeHome) {
|
|
15436
|
-
const home =
|
|
16721
|
+
const home = homedir12();
|
|
15437
16722
|
for (const name of HOME_FILES) {
|
|
15438
|
-
const file =
|
|
16723
|
+
const file = join24(home, name);
|
|
15439
16724
|
if (!existsSync23(file))
|
|
15440
16725
|
continue;
|
|
15441
16726
|
const text = readTextFile(file);
|
|
@@ -15460,12 +16745,12 @@ function collectRepoFiles(root) {
|
|
|
15460
16745
|
if (entry.isDirectory()) {
|
|
15461
16746
|
if (SKIP_DIRS.has(entry.name))
|
|
15462
16747
|
continue;
|
|
15463
|
-
visit(
|
|
16748
|
+
visit(join24(dir, entry.name));
|
|
15464
16749
|
continue;
|
|
15465
16750
|
}
|
|
15466
16751
|
if (!entry.isFile())
|
|
15467
16752
|
continue;
|
|
15468
|
-
const file =
|
|
16753
|
+
const file = join24(dir, entry.name);
|
|
15469
16754
|
if (shouldScanRepoFile(file))
|
|
15470
16755
|
out.push(file);
|
|
15471
16756
|
}
|
|
@@ -15503,7 +16788,7 @@ function readTextFile(file) {
|
|
|
15503
16788
|
const stat = lstatSync7(file);
|
|
15504
16789
|
if (!stat.isFile() || stat.size > 5000000)
|
|
15505
16790
|
return null;
|
|
15506
|
-
const buf =
|
|
16791
|
+
const buf = readFileSync17(file);
|
|
15507
16792
|
if (buf.includes(0))
|
|
15508
16793
|
return null;
|
|
15509
16794
|
return buf.toString("utf-8");
|
|
@@ -15733,10 +17018,10 @@ function stripInlineComment(value) {
|
|
|
15733
17018
|
return value.replace(/\s[#;].*$/, "").trim();
|
|
15734
17019
|
}
|
|
15735
17020
|
function displayPath(file, root) {
|
|
15736
|
-
const home =
|
|
17021
|
+
const home = homedir12();
|
|
15737
17022
|
if (root === home && (file === home || file.startsWith(home + "/")))
|
|
15738
17023
|
return "~/" + toPosix(relative7(home, file));
|
|
15739
|
-
if (
|
|
17024
|
+
if (isAbsolute6(root) && file.startsWith(root + "/"))
|
|
15740
17025
|
return toPosix(relative7(root, file));
|
|
15741
17026
|
if (file === home || file.startsWith(home + "/"))
|
|
15742
17027
|
return "~/" + toPosix(relative7(home, file));
|
|
@@ -15788,12 +17073,12 @@ var init_package_manager_guard = __esm(() => {
|
|
|
15788
17073
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
15789
17074
|
import { Buffer as Buffer2 } from "buffer";
|
|
15790
17075
|
import { existsSync as existsSync2 } from "fs";
|
|
15791
|
-
import { join as
|
|
17076
|
+
import { join as join2 } from "path";
|
|
15792
17077
|
import { existsSync } from "fs";
|
|
15793
|
-
import { homedir as homedir2 } from "os";
|
|
15794
|
-
import { join as join2, resolve } from "path";
|
|
15795
17078
|
import { homedir } from "os";
|
|
15796
|
-
import { join } from "path";
|
|
17079
|
+
import { join, resolve } from "path";
|
|
17080
|
+
import { homedir as pathsResolverHomedir } from "os";
|
|
17081
|
+
import { join as pathsResolverJoin } from "path";
|
|
15797
17082
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
15798
17083
|
import { lookup as dnsLookup } from "dns/promises";
|
|
15799
17084
|
import { isIP } from "net";
|
|
@@ -15897,75 +17182,72 @@ function channelMatchesEvent(channel, event) {
|
|
|
15897
17182
|
return true;
|
|
15898
17183
|
return channel.filters.some((filter) => eventMatchesFilter(event, filter));
|
|
15899
17184
|
}
|
|
15900
|
-
var
|
|
17185
|
+
var PATHS_RESOLVER_KIND_ENV = {
|
|
15901
17186
|
config: "HASNA_CONFIG_HOME",
|
|
15902
17187
|
data: "HASNA_DATA_HOME",
|
|
15903
17188
|
state: "HASNA_STATE_HOME",
|
|
15904
17189
|
cache: "HASNA_CACHE_HOME"
|
|
15905
17190
|
};
|
|
15906
|
-
var
|
|
15907
|
-
function
|
|
17191
|
+
var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
17192
|
+
function pathsResolverAssertApp(app) {
|
|
15908
17193
|
if (typeof app !== "string" || app.length === 0) {
|
|
15909
17194
|
throw new TypeError("paths: app must be a non-empty string");
|
|
15910
17195
|
}
|
|
15911
|
-
if (!
|
|
17196
|
+
if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
|
|
15912
17197
|
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
15913
17198
|
}
|
|
15914
17199
|
}
|
|
15915
|
-
function
|
|
15916
|
-
|
|
15917
|
-
}
|
|
15918
|
-
|
|
15919
|
-
const value = envOf(options)[KIND_ENV[kind]];
|
|
15920
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
15921
|
-
}
|
|
15922
|
-
function isMacOS(platform) {
|
|
15923
|
-
return platform === "darwin";
|
|
17200
|
+
function pathsResolverAssertKind(kind) {
|
|
17201
|
+
if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
|
|
17202
|
+
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
|
|
17203
|
+
}
|
|
15924
17204
|
}
|
|
15925
|
-
function
|
|
15926
|
-
|
|
15927
|
-
|
|
17205
|
+
function pathsResolverBaseDir(kind, options) {
|
|
17206
|
+
pathsResolverAssertKind(kind);
|
|
17207
|
+
const env = options.env ?? process.env;
|
|
17208
|
+
const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
|
|
17209
|
+
if (typeof override === "string" && override.length > 0)
|
|
15928
17210
|
return override;
|
|
15929
|
-
const home = options.home ??
|
|
17211
|
+
const home = options.home ?? pathsResolverHomedir();
|
|
15930
17212
|
const platform = options.platform ?? process.platform;
|
|
15931
|
-
if (
|
|
17213
|
+
if (platform === "darwin") {
|
|
15932
17214
|
switch (kind) {
|
|
15933
17215
|
case "config":
|
|
15934
17216
|
case "data":
|
|
15935
|
-
return
|
|
17217
|
+
return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
|
|
15936
17218
|
case "cache":
|
|
15937
|
-
return
|
|
17219
|
+
return pathsResolverJoin(home, "Library", "Caches", "Hasna");
|
|
15938
17220
|
case "state":
|
|
15939
|
-
return
|
|
17221
|
+
return pathsResolverJoin(home, "Library", "Logs", "Hasna");
|
|
15940
17222
|
}
|
|
15941
17223
|
}
|
|
15942
17224
|
switch (kind) {
|
|
15943
17225
|
case "config":
|
|
15944
|
-
return
|
|
17226
|
+
return pathsResolverJoin(home, ".config", "hasna");
|
|
15945
17227
|
case "data":
|
|
15946
|
-
return
|
|
17228
|
+
return pathsResolverJoin(home, ".local", "share", "hasna");
|
|
15947
17229
|
case "state":
|
|
15948
|
-
return
|
|
17230
|
+
return pathsResolverJoin(home, ".local", "state", "hasna");
|
|
15949
17231
|
case "cache":
|
|
15950
|
-
return
|
|
17232
|
+
return pathsResolverJoin(home, ".cache", "hasna");
|
|
15951
17233
|
}
|
|
15952
17234
|
}
|
|
15953
|
-
function
|
|
15954
|
-
|
|
15955
|
-
const appSegment = options.internal === true ?
|
|
15956
|
-
return
|
|
17235
|
+
function pathsResolverResolve(kind, options) {
|
|
17236
|
+
pathsResolverAssertApp(options.app);
|
|
17237
|
+
const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
|
|
17238
|
+
return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
|
|
15957
17239
|
}
|
|
15958
17240
|
function dataDir(options) {
|
|
15959
|
-
return
|
|
17241
|
+
return pathsResolverResolve("data", options);
|
|
15960
17242
|
}
|
|
15961
17243
|
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
15962
17244
|
var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
15963
17245
|
var EVENTS_STORE_SENTINEL_FILE = "events.json";
|
|
15964
17246
|
function effectiveHome() {
|
|
15965
|
-
return process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
17247
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
15966
17248
|
}
|
|
15967
17249
|
function legacyHomeDir() {
|
|
15968
|
-
return
|
|
17250
|
+
return join(effectiveHome(), ".hasna", "events");
|
|
15969
17251
|
}
|
|
15970
17252
|
function resolverHome() {
|
|
15971
17253
|
return dataDir({ app: "events", home: effectiveHome() || undefined });
|
|
@@ -15974,7 +17256,7 @@ function adoptResolverHome(resolved, env = process.env) {
|
|
|
15974
17256
|
const dataOverride = env.HASNA_DATA_HOME;
|
|
15975
17257
|
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
15976
17258
|
return true;
|
|
15977
|
-
return existsSync(
|
|
17259
|
+
return existsSync(join(resolved, EVENTS_STORE_SENTINEL_FILE));
|
|
15978
17260
|
}
|
|
15979
17261
|
function exactEventsHome() {
|
|
15980
17262
|
const dir = process.env[HASNA_EVENTS_DIR_ENV];
|
|
@@ -16015,9 +17297,9 @@ class JsonEventsStore {
|
|
|
16015
17297
|
constructor(dataDir2 = getEventsDataDir()) {
|
|
16016
17298
|
this.dataDir = dataDir2;
|
|
16017
17299
|
this.runtime = localJsonRuntime(dataDir2);
|
|
16018
|
-
this.channelsPath =
|
|
16019
|
-
this.eventsPath =
|
|
16020
|
-
this.deliveriesPath =
|
|
17300
|
+
this.channelsPath = join2(dataDir2, "channels.json");
|
|
17301
|
+
this.eventsPath = join2(dataDir2, "events.json");
|
|
17302
|
+
this.deliveriesPath = join2(dataDir2, "deliveries.json");
|
|
16021
17303
|
}
|
|
16022
17304
|
async init() {
|
|
16023
17305
|
await mkdir(this.dataDir, { recursive: true, mode: 448 });
|
|
@@ -16285,7 +17567,7 @@ async function getEventsStatus(dataDir2) {
|
|
|
16285
17567
|
};
|
|
16286
17568
|
}
|
|
16287
17569
|
function statusFile(dataDir2, fileName, records) {
|
|
16288
|
-
const path =
|
|
17570
|
+
const path = join2(dataDir2, fileName);
|
|
16289
17571
|
return { path, exists: existsSync2(path), records };
|
|
16290
17572
|
}
|
|
16291
17573
|
var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
@@ -17219,6 +18501,13 @@ function parseMatcherExpression(value, label) {
|
|
|
17219
18501
|
negated: false
|
|
17220
18502
|
};
|
|
17221
18503
|
}
|
|
18504
|
+
function webhookTargetPolicyFromEnv() {
|
|
18505
|
+
const value = process.env.HASNA_EVENTS_ALLOW_PRIVATE_WEBHOOK_TARGETS;
|
|
18506
|
+
if (!value)
|
|
18507
|
+
return;
|
|
18508
|
+
const hosts = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
18509
|
+
return hosts.length > 0 ? { allowPrivateHosts: hosts } : undefined;
|
|
18510
|
+
}
|
|
17222
18511
|
var DEFAULT_EVENT_LIST_LIMIT = 100;
|
|
17223
18512
|
function parseJsonObject(value, fallback) {
|
|
17224
18513
|
if (!value)
|
|
@@ -17244,7 +18533,7 @@ function parseHeaders(values) {
|
|
|
17244
18533
|
function createClient(options) {
|
|
17245
18534
|
if (options.createClient)
|
|
17246
18535
|
return options.createClient();
|
|
17247
|
-
return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
|
|
18536
|
+
return new EventsClient({ store: new JsonEventsStore(options.dataDir), webhookTargetPolicy: webhookTargetPolicyFromEnv() });
|
|
17248
18537
|
}
|
|
17249
18538
|
function print(value, json, text) {
|
|
17250
18539
|
if (json)
|
|
@@ -17325,6 +18614,8 @@ function registerChannelCommands(program, options) {
|
|
|
17325
18614
|
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
17326
18615
|
}, { honorFilters: actionOptions.honorFilters });
|
|
17327
18616
|
print(result, json, `${result.status}: ${result.channelId}`);
|
|
18617
|
+
if (result.status === "failed")
|
|
18618
|
+
process.exitCode = 1;
|
|
17328
18619
|
} catch (error) {
|
|
17329
18620
|
fail(error, json);
|
|
17330
18621
|
}
|
|
@@ -17433,9 +18724,9 @@ var {
|
|
|
17433
18724
|
// src/cli/index.tsx
|
|
17434
18725
|
init_apply();
|
|
17435
18726
|
import chalk from "chalk";
|
|
17436
|
-
import { existsSync as existsSync24, lstatSync as lstatSync8, readFileSync as
|
|
17437
|
-
import { homedir as
|
|
17438
|
-
import { basename as basename8, join as
|
|
18727
|
+
import { existsSync as existsSync24, lstatSync as lstatSync8, readFileSync as readFileSync18, readSync, writeSync } from "fs";
|
|
18728
|
+
import { homedir as homedir13 } from "os";
|
|
18729
|
+
import { basename as basename8, join as join25, resolve as resolve16 } from "path";
|
|
17439
18730
|
|
|
17440
18731
|
// src/lib/config-target-identity.ts
|
|
17441
18732
|
init_apply();
|
|
@@ -17482,14 +18773,14 @@ init_redact();
|
|
|
17482
18773
|
// src/lib/export.ts
|
|
17483
18774
|
init_config_store();
|
|
17484
18775
|
import { existsSync as existsSync15, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
17485
|
-
import { join as
|
|
18776
|
+
import { join as join17, resolve as resolve11 } from "path";
|
|
17486
18777
|
import { tmpdir } from "os";
|
|
17487
18778
|
async function exportConfigs(outputPath, opts = {}) {
|
|
17488
18779
|
const store = opts.store ?? resolveConfigStore();
|
|
17489
18780
|
const configs = await store.listConfigs(opts.filter);
|
|
17490
18781
|
const absOutput = resolve11(outputPath);
|
|
17491
|
-
const tmpDir =
|
|
17492
|
-
const contentsDir =
|
|
18782
|
+
const tmpDir = join17(tmpdir(), `configs-export-${Date.now()}`);
|
|
18783
|
+
const contentsDir = join17(tmpDir, "contents");
|
|
17493
18784
|
try {
|
|
17494
18785
|
mkdirSync4(contentsDir, { recursive: true });
|
|
17495
18786
|
const manifest = {
|
|
@@ -17497,10 +18788,10 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
17497
18788
|
exported_at: new Date().toISOString(),
|
|
17498
18789
|
configs: configs.map(({ content: _content, ...meta }) => meta)
|
|
17499
18790
|
};
|
|
17500
|
-
writeFileSync3(
|
|
18791
|
+
writeFileSync3(join17(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf-8");
|
|
17501
18792
|
for (const config of configs) {
|
|
17502
18793
|
const fileName = `${config.slug}.${config.format === "text" ? "txt" : config.format}`;
|
|
17503
|
-
writeFileSync3(
|
|
18794
|
+
writeFileSync3(join17(contentsDir, fileName), config.content, "utf-8");
|
|
17504
18795
|
}
|
|
17505
18796
|
const proc = Bun.spawn(["tar", "czf", absOutput, "-C", tmpDir, "."], {
|
|
17506
18797
|
stdout: "pipe",
|
|
@@ -17521,14 +18812,14 @@ async function exportConfigs(outputPath, opts = {}) {
|
|
|
17521
18812
|
|
|
17522
18813
|
// src/lib/import.ts
|
|
17523
18814
|
init_config_store();
|
|
17524
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as
|
|
17525
|
-
import { join as
|
|
18815
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync10, rmSync as rmSync4 } from "fs";
|
|
18816
|
+
import { join as join18, resolve as resolve12 } from "path";
|
|
17526
18817
|
import { tmpdir as tmpdir2 } from "os";
|
|
17527
18818
|
async function importConfigs(bundlePath, opts = {}) {
|
|
17528
18819
|
const store = opts.store ?? resolveConfigStore();
|
|
17529
18820
|
const conflict = opts.conflict ?? "skip";
|
|
17530
18821
|
const absPath = resolve12(bundlePath);
|
|
17531
|
-
const tmpDir =
|
|
18822
|
+
const tmpDir = join18(tmpdir2(), `configs-import-${Date.now()}`);
|
|
17532
18823
|
const result = { created: 0, updated: 0, skipped: 0, errors: [] };
|
|
17533
18824
|
try {
|
|
17534
18825
|
mkdirSync5(tmpDir, { recursive: true });
|
|
@@ -17541,15 +18832,15 @@ async function importConfigs(bundlePath, opts = {}) {
|
|
|
17541
18832
|
const stderr = await new Response(proc.stderr).text();
|
|
17542
18833
|
throw new Error(`tar extraction failed: ${stderr}`);
|
|
17543
18834
|
}
|
|
17544
|
-
const manifestPath =
|
|
18835
|
+
const manifestPath = join18(tmpDir, "manifest.json");
|
|
17545
18836
|
if (!existsSync16(manifestPath))
|
|
17546
18837
|
throw new Error("Invalid bundle: missing manifest.json");
|
|
17547
|
-
const manifest = JSON.parse(
|
|
18838
|
+
const manifest = JSON.parse(readFileSync10(manifestPath, "utf-8"));
|
|
17548
18839
|
for (const meta of manifest.configs) {
|
|
17549
18840
|
try {
|
|
17550
18841
|
const ext = meta.format === "text" ? "txt" : meta.format;
|
|
17551
|
-
const contentFile =
|
|
17552
|
-
const content = existsSync16(contentFile) ?
|
|
18842
|
+
const contentFile = join18(tmpDir, "contents", `${meta.slug}.${ext}`);
|
|
18843
|
+
const content = existsSync16(contentFile) ? readFileSync10(contentFile, "utf-8") : "";
|
|
17553
18844
|
let existing = null;
|
|
17554
18845
|
try {
|
|
17555
18846
|
existing = await store.getConfig(meta.slug);
|
|
@@ -17604,11 +18895,11 @@ import {
|
|
|
17604
18895
|
existsSync as existsSync17,
|
|
17605
18896
|
lstatSync as lstatSync4,
|
|
17606
18897
|
mkdirSync as mkdirSync6,
|
|
17607
|
-
readFileSync as
|
|
18898
|
+
readFileSync as readFileSync11,
|
|
17608
18899
|
readdirSync as readdirSync4,
|
|
17609
18900
|
statSync as statSync6
|
|
17610
18901
|
} from "fs";
|
|
17611
|
-
import { dirname as dirname7, isAbsolute as
|
|
18902
|
+
import { dirname as dirname7, isAbsolute as isAbsolute5, join as join19, parse as parse4, relative as relative5, resolve as resolve13 } from "path";
|
|
17612
18903
|
|
|
17613
18904
|
class SessionApplyError extends Error {
|
|
17614
18905
|
constructor(message) {
|
|
@@ -17773,7 +19064,7 @@ function checkSessionRenderDrift(targetHome, manifestPath) {
|
|
|
17773
19064
|
});
|
|
17774
19065
|
continue;
|
|
17775
19066
|
}
|
|
17776
|
-
const actualSha256 = sha2568(
|
|
19067
|
+
const actualSha256 = sha2568(readFileSync11(target, "utf-8"));
|
|
17777
19068
|
if (actualSha256 !== file.sha256) {
|
|
17778
19069
|
drifted.push({
|
|
17779
19070
|
path: target,
|
|
@@ -17799,10 +19090,10 @@ function restoreSessionRenderSnapshot(snapshotPath, options = {}) {
|
|
|
17799
19090
|
const resolvedSnapshotPath = resolve13(snapshotPath);
|
|
17800
19091
|
const snapshotDir = getSessionRenderSnapshotDir(targetHome);
|
|
17801
19092
|
const snapshotDirRelative = relative5(snapshotDir, resolvedSnapshotPath);
|
|
17802
|
-
const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !
|
|
19093
|
+
const insideSnapshotDir = snapshotDirRelative !== ".." && !snapshotDirRelative.startsWith("../") && !isAbsolute5(snapshotDirRelative);
|
|
17803
19094
|
if (!insideSnapshotDir) {
|
|
17804
19095
|
const snapshotRelativePath = relative5(targetHome, resolvedSnapshotPath);
|
|
17805
|
-
if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") ||
|
|
19096
|
+
if (snapshotRelativePath === "" || snapshotRelativePath === ".." || snapshotRelativePath.startsWith("../") || isAbsolute5(snapshotRelativePath)) {
|
|
17806
19097
|
throw new SessionApplyError("Session snapshot must be stored inside its session-render snapshot location.");
|
|
17807
19098
|
}
|
|
17808
19099
|
}
|
|
@@ -17931,7 +19222,7 @@ function readSessionRenderSnapshot(snapshotPath) {
|
|
|
17931
19222
|
}
|
|
17932
19223
|
let parsed;
|
|
17933
19224
|
try {
|
|
17934
|
-
parsed = JSON.parse(
|
|
19225
|
+
parsed = JSON.parse(readFileSync11(resolved, "utf8"));
|
|
17935
19226
|
} catch {
|
|
17936
19227
|
throw new SessionApplyError(`Session snapshot is not valid JSON: ${snapshotPath}`);
|
|
17937
19228
|
}
|
|
@@ -18009,7 +19300,7 @@ function reconstructPreRollbackLegacyV1Snapshot(snapshot, previousFiles, previou
|
|
|
18009
19300
|
}
|
|
18010
19301
|
let parsedManifest;
|
|
18011
19302
|
try {
|
|
18012
|
-
parsedManifest = JSON.parse(
|
|
19303
|
+
parsedManifest = JSON.parse(readFileSync11(manifestPath, "utf8"));
|
|
18013
19304
|
} catch {
|
|
18014
19305
|
throw new SessionApplyError(`Pre-rollback legacy v1 applied manifest is not valid JSON: ${snapshotPath}`);
|
|
18015
19306
|
}
|
|
@@ -18109,7 +19400,7 @@ function assertNoNewerSessionSnapshot(snapshotPath, createdAt, targetHome) {
|
|
|
18109
19400
|
if (candidateStat.isSymbolicLink() || !candidateStat.isFile() || candidateStat.size > 32 * 1024 * 1024)
|
|
18110
19401
|
continue;
|
|
18111
19402
|
try {
|
|
18112
|
-
const candidate = JSON.parse(
|
|
19403
|
+
const candidate = JSON.parse(readFileSync11(candidatePath, "utf8"));
|
|
18113
19404
|
const candidateCreatedAtMs = typeof candidate.createdAt === "string" ? Date.parse(candidate.createdAt) : Number.NaN;
|
|
18114
19405
|
if ((candidate.schema === "hasna.configs.session-render-snapshot/v1" || candidate.schema === "hasna.configs.session-render-snapshot/v2") && typeof candidate.targetHome === "string" && resolve13(candidate.targetHome) === targetHome && Number.isFinite(candidateCreatedAtMs) && candidateCreatedAtMs >= createdAtMs) {
|
|
18115
19406
|
throw new SessionApplyError(`Cannot restore pre-rollback legacy v1 snapshot after a newer session snapshot exists: ${candidatePath}`);
|
|
@@ -18187,7 +19478,7 @@ function resolveSnapshotFilePath(relativePath, recordedPath, targetHome) {
|
|
|
18187
19478
|
}
|
|
18188
19479
|
function planFileResult(plan, file, targetHome, previousHashes, previousManifest, options) {
|
|
18189
19480
|
const target = resolvePlannedFilePath(plan, file, targetHome);
|
|
18190
|
-
const previousContent = existsSync17(target) ?
|
|
19481
|
+
const previousContent = existsSync17(target) ? readFileSync11(target, "utf-8") : null;
|
|
18191
19482
|
const previousSha256 = previousContent === null ? null : sha2568(previousContent);
|
|
18192
19483
|
const previouslyManaged = isPreviouslyManaged(file, previousSha256, previousHashes, previousManifest);
|
|
18193
19484
|
const changed = previousContent !== file.content;
|
|
@@ -18288,7 +19579,7 @@ function planStaleFileResult(file, targetHome, options) {
|
|
|
18288
19579
|
const target = resolveManifestRelativePath(file.relativePath, targetHome);
|
|
18289
19580
|
if (!existsSync17(target))
|
|
18290
19581
|
return null;
|
|
18291
|
-
const previousContent =
|
|
19582
|
+
const previousContent = readFileSync11(target, "utf-8");
|
|
18292
19583
|
const previousSha256 = sha2568(previousContent);
|
|
18293
19584
|
if (!options.force && previousSha256 !== file.sha256) {
|
|
18294
19585
|
return {
|
|
@@ -18335,7 +19626,7 @@ function isPreviouslyManaged(file, previousSha256, previousHashes, previousManif
|
|
|
18335
19626
|
function resolvePlannedFilePath(plan, file, targetHome) {
|
|
18336
19627
|
const target = resolve13(targetHome, ...file.relativePath.split("/"));
|
|
18337
19628
|
const rel = relative5(targetHome, target);
|
|
18338
|
-
if (rel === "" || rel === ".." || rel.startsWith("../") ||
|
|
19629
|
+
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute5(rel)) {
|
|
18339
19630
|
throw new SessionApplyError(`Session file escapes target home: ${file.relativePath}`);
|
|
18340
19631
|
}
|
|
18341
19632
|
if (resolve13(file.path) !== target) {
|
|
@@ -18347,7 +19638,7 @@ function resolvePlannedFilePath(plan, file, targetHome) {
|
|
|
18347
19638
|
function resolveManifestRelativePath(relativePath, targetHome) {
|
|
18348
19639
|
const target = resolve13(targetHome, ...relativePath.split(/[\\/]+/));
|
|
18349
19640
|
const rel = relative5(targetHome, target);
|
|
18350
|
-
if (rel === "" || rel === ".." || rel.startsWith("../") ||
|
|
19641
|
+
if (rel === "" || rel === ".." || rel.startsWith("../") || isAbsolute5(rel)) {
|
|
18351
19642
|
throw new SessionApplyError(`Session manifest file escapes target home: ${relativePath}`);
|
|
18352
19643
|
}
|
|
18353
19644
|
assertNoSymlinkSegments2(targetHome, target);
|
|
@@ -18357,7 +19648,7 @@ function readPreviousManifest(path) {
|
|
|
18357
19648
|
if (!existsSync17(path))
|
|
18358
19649
|
return null;
|
|
18359
19650
|
try {
|
|
18360
|
-
const parsed = JSON.parse(
|
|
19651
|
+
const parsed = JSON.parse(readFileSync11(path, "utf-8"));
|
|
18361
19652
|
if (parsed.schema !== SESSION_RENDER_SCHEMA)
|
|
18362
19653
|
return null;
|
|
18363
19654
|
if (!Array.isArray(parsed.files))
|
|
@@ -18402,7 +19693,7 @@ function currentSessionFileHash(path, targetHome) {
|
|
|
18402
19693
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
18403
19694
|
throw new SessionApplyError(`Session apply path is not a regular file: ${path}`);
|
|
18404
19695
|
}
|
|
18405
|
-
return sha2568(
|
|
19696
|
+
return sha2568(readFileSync11(path, "utf-8"));
|
|
18406
19697
|
}
|
|
18407
19698
|
function requiredPreviousHash(result) {
|
|
18408
19699
|
if (result.previousSha256 === null) {
|
|
@@ -18412,7 +19703,7 @@ function requiredPreviousHash(result) {
|
|
|
18412
19703
|
}
|
|
18413
19704
|
function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousManifest, coordination, allowPortableFallback, forcePortableFileOps) {
|
|
18414
19705
|
const existingFiles = results.filter((result) => result.action === "update" || result.action === "delete").filter((result) => existsSync17(result.path)).map((result) => {
|
|
18415
|
-
const content =
|
|
19706
|
+
const content = readFileSync11(result.path, "utf-8");
|
|
18416
19707
|
return {
|
|
18417
19708
|
path: result.path,
|
|
18418
19709
|
relativePath: result.relativePath,
|
|
@@ -18430,7 +19721,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
18430
19721
|
};
|
|
18431
19722
|
}
|
|
18432
19723
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
18433
|
-
const snapshotPath =
|
|
19724
|
+
const snapshotPath = join19(getSessionRenderSnapshotDir(targetHome), `${timestamp}-${randomUUID7()}.json`);
|
|
18434
19725
|
const afterFiles = results.map((result) => {
|
|
18435
19726
|
if (result.action === "conflict") {
|
|
18436
19727
|
throw new SessionApplyError(`Cannot snapshot unresolved conflict: ${result.relativePath}`);
|
|
@@ -18476,7 +19767,7 @@ function writeSessionSnapshot(plan, targetHome, manifestPath, results, previousM
|
|
|
18476
19767
|
};
|
|
18477
19768
|
}
|
|
18478
19769
|
function assertSafeTargetHome(targetHome) {
|
|
18479
|
-
if (!
|
|
19770
|
+
if (!isAbsolute5(targetHome))
|
|
18480
19771
|
throw new SessionApplyError(`Session target home must be absolute: ${targetHome}`);
|
|
18481
19772
|
const normalized = resolve13(targetHome);
|
|
18482
19773
|
if (normalized === parse4(normalized).root) {
|
|
@@ -18493,7 +19784,7 @@ function assertNoSymlinkSegments2(root, target) {
|
|
|
18493
19784
|
const rel = relative5(root, target);
|
|
18494
19785
|
let current = root;
|
|
18495
19786
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
18496
|
-
current =
|
|
19787
|
+
current = join19(current, segment);
|
|
18497
19788
|
if (existsSync17(current) && lstatSync4(current).isSymbolicLink()) {
|
|
18498
19789
|
throw new SessionApplyError(`Session apply path uses a symlink: ${current}`);
|
|
18499
19790
|
}
|
|
@@ -18505,7 +19796,7 @@ function assertNoSymlinkAncestors2(path) {
|
|
|
18505
19796
|
let current = parsed.root;
|
|
18506
19797
|
const rel = relative5(parsed.root, normalized);
|
|
18507
19798
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
18508
|
-
current =
|
|
19799
|
+
current = join19(current, segment);
|
|
18509
19800
|
if (!existsSync17(current))
|
|
18510
19801
|
return;
|
|
18511
19802
|
if (lstatSync4(current).isSymbolicLink()) {
|
|
@@ -18556,10 +19847,10 @@ function formatGlobalSourceCoverageWarnings(result) {
|
|
|
18556
19847
|
|
|
18557
19848
|
// src/lib/station-profile.ts
|
|
18558
19849
|
init_raw_store_root();
|
|
18559
|
-
import { spawnSync } from "child_process";
|
|
18560
|
-
import { existsSync as existsSync18, lstatSync as lstatSync5, mkdirSync as mkdirSync7, readFileSync as
|
|
18561
|
-
import { arch as osArch, homedir as
|
|
18562
|
-
import { dirname as dirname8, join as
|
|
19850
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
19851
|
+
import { existsSync as existsSync18, lstatSync as lstatSync5, mkdirSync as mkdirSync7, readFileSync as readFileSync12, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
19852
|
+
import { arch as osArch, homedir as homedir10, hostname as osHostname2, platform as osPlatform, userInfo as osUserInfo } from "os";
|
|
19853
|
+
import { dirname as dirname8, join as join20 } from "path";
|
|
18563
19854
|
var STATION_PROFILE_CACHE_FILENAME = "station-profile.md";
|
|
18564
19855
|
var STATION_PROFILE_SOURCE_ID = "station-profile";
|
|
18565
19856
|
var STATION_PROFILE_LAYER = "machine";
|
|
@@ -18569,23 +19860,23 @@ var STATION_PROFILE_FULL_NAMES_MAX = 6;
|
|
|
18569
19860
|
var STATION_PROFILE_PRIMARY_SCOPE = "@hasna";
|
|
18570
19861
|
var MACHINES_MANIFEST_PATH_ENV = "HASNA_MACHINES_MANIFEST_PATH";
|
|
18571
19862
|
var BUN_INSTALL_ENV = "BUN_INSTALL";
|
|
18572
|
-
function
|
|
18573
|
-
return env["HOME"] || env["USERPROFILE"] ||
|
|
19863
|
+
function homeDir5(env = process.env) {
|
|
19864
|
+
return env["HOME"] || env["USERPROFILE"] || homedir10();
|
|
18574
19865
|
}
|
|
18575
19866
|
function getStationProfileCachePath(env = process.env) {
|
|
18576
|
-
return
|
|
19867
|
+
return join20(getRawStoreRoot(env), STATION_PROFILE_CACHE_FILENAME);
|
|
18577
19868
|
}
|
|
18578
19869
|
function getMachinesManifestPath(env = process.env) {
|
|
18579
|
-
return env[MACHINES_MANIFEST_PATH_ENV] ||
|
|
19870
|
+
return env[MACHINES_MANIFEST_PATH_ENV] || join20(homeDir5(env), ".hasna", "machines", "machines.json");
|
|
18580
19871
|
}
|
|
18581
19872
|
function getBunGlobalModulesDir(env = process.env) {
|
|
18582
|
-
return
|
|
19873
|
+
return join20(env[BUN_INSTALL_ENV] || join20(homeDir5(env), ".bun"), "install", "global", "node_modules");
|
|
18583
19874
|
}
|
|
18584
19875
|
function readMachinesManifest(path) {
|
|
18585
19876
|
try {
|
|
18586
19877
|
if (!existsSync18(path))
|
|
18587
19878
|
return null;
|
|
18588
|
-
const parsed = JSON.parse(
|
|
19879
|
+
const parsed = JSON.parse(readFileSync12(path, "utf8"));
|
|
18589
19880
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
18590
19881
|
return null;
|
|
18591
19882
|
const machines = parsed["machines"];
|
|
@@ -18615,7 +19906,7 @@ function metadataUser(record) {
|
|
|
18615
19906
|
}
|
|
18616
19907
|
function probeMachineStatus(machineId) {
|
|
18617
19908
|
try {
|
|
18618
|
-
const result =
|
|
19909
|
+
const result = spawnSync2("machines", ["details", "--json", "--machine", machineId], {
|
|
18619
19910
|
encoding: "utf8",
|
|
18620
19911
|
timeout: 3000,
|
|
18621
19912
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -18637,11 +19928,11 @@ function probeMachineStatus(machineId) {
|
|
|
18637
19928
|
}
|
|
18638
19929
|
}
|
|
18639
19930
|
function resolveStationProfileMachine(env = process.env, options = {}) {
|
|
18640
|
-
const hostname2 =
|
|
19931
|
+
const hostname2 = osHostname2();
|
|
18641
19932
|
const record = findLocalManifestMachine(readMachinesManifest(getMachinesManifestPath(env)), hostname2);
|
|
18642
|
-
const home =
|
|
19933
|
+
const home = homeDir5(env);
|
|
18643
19934
|
const platform = stringField(record, "platform") ?? osPlatform();
|
|
18644
|
-
const workspacePath = stringField(record, "workspacePath") ??
|
|
19935
|
+
const workspacePath = stringField(record, "workspacePath") ?? join20(home, platform === "darwin" ? "Workspace" : "workspace");
|
|
18645
19936
|
const machine = {
|
|
18646
19937
|
id: stringField(record, "id") ?? hostname2,
|
|
18647
19938
|
hostname: stringField(record, "hostname") ?? hostname2,
|
|
@@ -18658,7 +19949,7 @@ function resolveStationProfileMachine(env = process.env, options = {}) {
|
|
|
18658
19949
|
return machine;
|
|
18659
19950
|
}
|
|
18660
19951
|
function scopedPackageNames(modulesDir, scope) {
|
|
18661
|
-
const scopeDir =
|
|
19952
|
+
const scopeDir = join20(modulesDir, scope);
|
|
18662
19953
|
try {
|
|
18663
19954
|
if (!existsSync18(scopeDir))
|
|
18664
19955
|
return null;
|
|
@@ -18670,7 +19961,7 @@ function scopedPackageNames(modulesDir, scope) {
|
|
|
18670
19961
|
function readdirNames(dir) {
|
|
18671
19962
|
return readdirSync5(dir).filter((name) => {
|
|
18672
19963
|
try {
|
|
18673
|
-
return lstatSync5(
|
|
19964
|
+
return lstatSync5(join20(dir, name)).isDirectory();
|
|
18674
19965
|
} catch {
|
|
18675
19966
|
return false;
|
|
18676
19967
|
}
|
|
@@ -18755,7 +20046,7 @@ function refreshStationProfile(options = {}) {
|
|
|
18755
20046
|
const path = getStationProfileCachePath(env);
|
|
18756
20047
|
const generatedAt = new Date().toISOString();
|
|
18757
20048
|
if (!options.dryRun) {
|
|
18758
|
-
const existing = existsSync18(path) ?
|
|
20049
|
+
const existing = existsSync18(path) ? readFileSync12(path, "utf8") : null;
|
|
18759
20050
|
if (existing !== content) {
|
|
18760
20051
|
mkdirSync7(dirname8(path), { recursive: true });
|
|
18761
20052
|
writeFileSync4(path, content, "utf8");
|
|
@@ -18776,7 +20067,7 @@ function readStationProfile(env = process.env) {
|
|
|
18776
20067
|
try {
|
|
18777
20068
|
if (!existsSync18(path))
|
|
18778
20069
|
return null;
|
|
18779
|
-
return
|
|
20070
|
+
return readFileSync12(path, "utf8");
|
|
18780
20071
|
} catch {
|
|
18781
20072
|
return null;
|
|
18782
20073
|
}
|
|
@@ -18803,9 +20094,6 @@ init_config_store();
|
|
|
18803
20094
|
init_config_store();
|
|
18804
20095
|
var PROJECT_DASHBOARD_STANDARD_SLUG = "agent-managed-project-dashboard-standard";
|
|
18805
20096
|
var PROJECT_DASHBOARD_PROFILE_VARIABLES = {
|
|
18806
|
-
PROJECT_DASHBOARD_DIR: ".hasna/project",
|
|
18807
|
-
PROJECT_DASHBOARD_RENDER_MANIFEST: ".hasna/project/dashboard/render.json",
|
|
18808
|
-
PROJECT_DASHBOARD_SNAPSHOTS_DIR: ".hasna/project/dashboard/snapshots",
|
|
18809
20097
|
PROJECT_CHANNEL_PREFIX: ""
|
|
18810
20098
|
};
|
|
18811
20099
|
var PROJECT_DASHBOARD_STANDARD_CONTENT = `# Agent-Managed Project Dashboard Standard
|
|
@@ -18816,9 +20104,13 @@ evidence, tasks, knowledge, and dashboard output consistent.
|
|
|
18816
20104
|
|
|
18817
20105
|
## Canonical Files
|
|
18818
20106
|
|
|
18819
|
-
|
|
18820
|
-
|
|
18821
|
-
|
|
20107
|
+
There is exactly one project-layout convention: the canonical per-workspace
|
|
20108
|
+
store \`~/.hasna/projects/workspaces/<workspace_id>/\`. Never create a project
|
|
20109
|
+
layout directory inside the project folder itself.
|
|
20110
|
+
|
|
20111
|
+
- Per-workspace store root: \`~/.hasna/projects/workspaces/<workspace_id>/\`
|
|
20112
|
+
- Dashboard render manifest: \`~/.hasna/projects/workspaces/<workspace_id>/dashboard/render.json\`
|
|
20113
|
+
- Latest snapshot: \`~/.hasna/projects/workspaces/<workspace_id>/dashboard/snapshots/latest.snapshot.json\`
|
|
18822
20114
|
- Dashboard schema ids come from \`@hasna/contracts\`.
|
|
18823
20115
|
- Project folders may contain private documents, but render JSON must contain
|
|
18824
20116
|
only ids, counts, statuses, resource refs, evidence refs, and redacted
|
|
@@ -19111,18 +20403,18 @@ init_codewith_shared_todos_storage_standard();
|
|
|
19111
20403
|
|
|
19112
20404
|
// src/lib/managed-skill-runtimes.ts
|
|
19113
20405
|
import { createHash as createHash9 } from "crypto";
|
|
19114
|
-
import { spawnSync as
|
|
20406
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
19115
20407
|
import {
|
|
19116
20408
|
existsSync as existsSync19,
|
|
19117
20409
|
lstatSync as lstatSync6,
|
|
19118
20410
|
mkdirSync as mkdirSync8,
|
|
19119
|
-
readFileSync as
|
|
20411
|
+
readFileSync as readFileSync13,
|
|
19120
20412
|
renameSync as renameSync2,
|
|
19121
20413
|
rmSync as rmSync5,
|
|
19122
20414
|
writeFileSync as writeFileSync5
|
|
19123
20415
|
} from "fs";
|
|
19124
|
-
import { homedir as
|
|
19125
|
-
import { dirname as dirname9, join as
|
|
20416
|
+
import { homedir as homedir11 } from "os";
|
|
20417
|
+
import { dirname as dirname9, join as join21, parse as parse5, relative as relative6, resolve as resolve14 } from "path";
|
|
19126
20418
|
var INBOX_CONVERSATIONS_MINIMUM_VERSION = "0.5.28";
|
|
19127
20419
|
var INBOX_SKILL_MARKERS = [
|
|
19128
20420
|
[".claude", "skills", "inbox", "SKILL.md"],
|
|
@@ -19148,7 +20440,7 @@ function findSymlinkedAncestor(path) {
|
|
|
19148
20440
|
let current = parsed.root;
|
|
19149
20441
|
const rel = relative6(parsed.root, normalized);
|
|
19150
20442
|
for (const segment of rel.split(/[\\/]+/).filter(Boolean)) {
|
|
19151
|
-
current =
|
|
20443
|
+
current = join21(current, segment);
|
|
19152
20444
|
if (!existsSync19(current))
|
|
19153
20445
|
return null;
|
|
19154
20446
|
if (lstatSync6(current).isSymbolicLink())
|
|
@@ -19166,9 +20458,9 @@ function packagedInboxSkillPath(explicitPath) {
|
|
|
19166
20458
|
if (explicitPath)
|
|
19167
20459
|
return explicitPath;
|
|
19168
20460
|
const candidates = [
|
|
19169
|
-
|
|
19170
|
-
|
|
19171
|
-
|
|
20461
|
+
join21(import.meta.dir, "..", "..", "assets", "skills", "inbox", "SKILL.md"),
|
|
20462
|
+
join21(import.meta.dir, "..", "assets", "skills", "inbox", "SKILL.md"),
|
|
20463
|
+
join21(process.cwd(), "assets", "skills", "inbox", "SKILL.md")
|
|
19172
20464
|
];
|
|
19173
20465
|
const found = candidates.find((candidate) => existsSync19(candidate));
|
|
19174
20466
|
if (!found) {
|
|
@@ -19182,7 +20474,7 @@ function readCanonicalSkill(explicitPath) {
|
|
|
19182
20474
|
if (!stat?.isFile()) {
|
|
19183
20475
|
throw new Error("packaged inbox skill contract is not a regular file");
|
|
19184
20476
|
}
|
|
19185
|
-
const content =
|
|
20477
|
+
const content = readFileSync13(assetPath, "utf8");
|
|
19186
20478
|
if (!content.includes("conversations watch --from <agent> --all")) {
|
|
19187
20479
|
throw new Error("packaged inbox skill contract does not declare the canonical conversations watcher");
|
|
19188
20480
|
}
|
|
@@ -19192,7 +20484,7 @@ function readCanonicalSkill(explicitPath) {
|
|
|
19192
20484
|
return { content, sha256: sha2569(content) };
|
|
19193
20485
|
}
|
|
19194
20486
|
function runProbe(command, args) {
|
|
19195
|
-
const result =
|
|
20487
|
+
const result = spawnSync3(command, args, {
|
|
19196
20488
|
encoding: "utf8",
|
|
19197
20489
|
timeout: 5000,
|
|
19198
20490
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -19219,8 +20511,8 @@ function compareVersions(left, right) {
|
|
|
19219
20511
|
}
|
|
19220
20512
|
return 0;
|
|
19221
20513
|
}
|
|
19222
|
-
function inspectSkillMarkers(
|
|
19223
|
-
return INBOX_SKILL_MARKERS.map((parts) =>
|
|
20514
|
+
function inspectSkillMarkers(homeDir6) {
|
|
20515
|
+
return INBOX_SKILL_MARKERS.map((parts) => join21(homeDir6, ...parts)).map((path) => {
|
|
19224
20516
|
const stat = lstatOrNull(path);
|
|
19225
20517
|
if (!stat)
|
|
19226
20518
|
return null;
|
|
@@ -19229,16 +20521,16 @@ function inspectSkillMarkers(homeDir5) {
|
|
|
19229
20521
|
}
|
|
19230
20522
|
return {
|
|
19231
20523
|
path,
|
|
19232
|
-
content:
|
|
20524
|
+
content: readFileSync13(path, "utf8"),
|
|
19233
20525
|
mode: stat.mode & 511,
|
|
19234
20526
|
regular: true
|
|
19235
20527
|
};
|
|
19236
20528
|
}).filter((snapshot) => snapshot !== null);
|
|
19237
20529
|
}
|
|
19238
20530
|
function inspectInbox(options) {
|
|
19239
|
-
const
|
|
20531
|
+
const homeDir6 = options.homeDir ?? homedir11();
|
|
19240
20532
|
const runtimeCommand = options.conversationsCommand ?? "conversations";
|
|
19241
|
-
const snapshots = inspectSkillMarkers(
|
|
20533
|
+
const snapshots = inspectSkillMarkers(homeDir6);
|
|
19242
20534
|
const skillPresent = snapshots.length > 0;
|
|
19243
20535
|
let canonicalContent = null;
|
|
19244
20536
|
let canonicalSha256 = null;
|
|
@@ -19363,7 +20655,7 @@ function writeAtomic(path, content, mode) {
|
|
|
19363
20655
|
}
|
|
19364
20656
|
var DEFAULT_SKILL_WRITE_FILE_OPERATIONS = {
|
|
19365
20657
|
lstat: lstatOrNull,
|
|
19366
|
-
read: (path) =>
|
|
20658
|
+
read: (path) => readFileSync13(path, "utf8"),
|
|
19367
20659
|
write: writeAtomic
|
|
19368
20660
|
};
|
|
19369
20661
|
function writeSkillContractsTransactional(snapshots, canonicalContent, fileOperations = DEFAULT_SKILL_WRITE_FILE_OPERATIONS) {
|
|
@@ -19513,11 +20805,11 @@ init_project_context();
|
|
|
19513
20805
|
init_config_store();
|
|
19514
20806
|
init_apply();
|
|
19515
20807
|
init_config_agents();
|
|
19516
|
-
import { existsSync as existsSync21, readFileSync as
|
|
20808
|
+
import { existsSync as existsSync21, readFileSync as readFileSync15 } from "fs";
|
|
19517
20809
|
|
|
19518
20810
|
// src/lib/package-version.ts
|
|
19519
|
-
import { existsSync as existsSync20, readFileSync as
|
|
19520
|
-
import { dirname as dirname10, join as
|
|
20811
|
+
import { existsSync as existsSync20, readFileSync as readFileSync14 } from "fs";
|
|
20812
|
+
import { dirname as dirname10, join as join22 } from "path";
|
|
19521
20813
|
import { fileURLToPath } from "url";
|
|
19522
20814
|
var cached = null;
|
|
19523
20815
|
function getPackageVersion() {
|
|
@@ -19526,9 +20818,9 @@ function getPackageVersion() {
|
|
|
19526
20818
|
try {
|
|
19527
20819
|
let dir = dirname10(fileURLToPath(import.meta.url));
|
|
19528
20820
|
for (let i = 0;i < 8; i++) {
|
|
19529
|
-
const pkgPath =
|
|
20821
|
+
const pkgPath = join22(dir, "package.json");
|
|
19530
20822
|
if (existsSync20(pkgPath)) {
|
|
19531
|
-
const pkg = JSON.parse(
|
|
20823
|
+
const pkg = JSON.parse(readFileSync14(pkgPath, "utf8"));
|
|
19532
20824
|
if (pkg.name === "@hasna/instructions" && pkg.version) {
|
|
19533
20825
|
cached = pkg.version;
|
|
19534
20826
|
return cached;
|
|
@@ -19595,7 +20887,7 @@ async function getConfigsStatus(store = resolveConfigStore(), options = {}) {
|
|
|
19595
20887
|
missingTargets += 1;
|
|
19596
20888
|
continue;
|
|
19597
20889
|
}
|
|
19598
|
-
const disk =
|
|
20890
|
+
const disk = readFileSync15(targetPath, "utf-8");
|
|
19599
20891
|
const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(config.target_path, config.format));
|
|
19600
20892
|
if (redactedDisk !== config.content) {
|
|
19601
20893
|
driftedTargets += 1;
|
|
@@ -19691,8 +20983,8 @@ init_config_store();
|
|
|
19691
20983
|
|
|
19692
20984
|
// src/lib/provider-context.ts
|
|
19693
20985
|
import { createHash as createHash10 } from "crypto";
|
|
19694
|
-
import { existsSync as existsSync22, mkdirSync as mkdirSync9, readFileSync as
|
|
19695
|
-
import { join as
|
|
20986
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync9, readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "fs";
|
|
20987
|
+
import { join as join23 } from "path";
|
|
19696
20988
|
var PROVIDER_CONTEXT_DIR = ".hasna/provider-context";
|
|
19697
20989
|
var PROVIDER_CONTEXT_MANIFEST = "manifest.json";
|
|
19698
20990
|
var PROVIDER_CONTEXT_SCHEMA = "hasna.instructions.provider-context/v1";
|
|
@@ -19837,18 +21129,18 @@ function resolveAndRenderProviderContext(opts) {
|
|
|
19837
21129
|
const recordedEndpoint = originAccepted ? `${opts.origin.host}${opts.origin.pathPrefix || ""}` : null;
|
|
19838
21130
|
const reason = entry === null && opts.rawEndpoint ? originAccepted ? `endpoint "${recordedEndpoint}" is not in the provider-context registry; using the invariant fragment` : "endpoint rejected (embedded credentials or unparseable); using the invariant fragment" : null;
|
|
19839
21131
|
const content = renderProviderFragment(entry);
|
|
19840
|
-
const dir =
|
|
21132
|
+
const dir = join23(opts.homeDir, PROVIDER_CONTEXT_DIR);
|
|
19841
21133
|
if (!existsSync22(dir))
|
|
19842
21134
|
mkdirSync9(dir, { recursive: true });
|
|
19843
21135
|
const filename = `${entry ? entry.key : "invariant"}.md`;
|
|
19844
|
-
const fragmentPath2 =
|
|
21136
|
+
const fragmentPath2 = join23(dir, filename);
|
|
19845
21137
|
const fragmentSha256 = sha25610(content);
|
|
19846
21138
|
writeFileSync6(fragmentPath2, content, "utf8");
|
|
19847
|
-
const manifestPath =
|
|
21139
|
+
const manifestPath = join23(dir, PROVIDER_CONTEXT_MANIFEST);
|
|
19848
21140
|
let manifest = { schema: PROVIDER_CONTEXT_SCHEMA, fragments: {} };
|
|
19849
21141
|
try {
|
|
19850
21142
|
if (existsSync22(manifestPath)) {
|
|
19851
|
-
const parsed = JSON.parse(
|
|
21143
|
+
const parsed = JSON.parse(readFileSync16(manifestPath, "utf8"));
|
|
19852
21144
|
if (parsed && typeof parsed === "object")
|
|
19853
21145
|
manifest = parsed;
|
|
19854
21146
|
}
|
|
@@ -19886,8 +21178,8 @@ function providerContextAuditLine(r, nowIso = new Date().toISOString()) {
|
|
|
19886
21178
|
}
|
|
19887
21179
|
|
|
19888
21180
|
// src/cli/index.tsx
|
|
19889
|
-
import { createRequire } from "module";
|
|
19890
|
-
var pkg =
|
|
21181
|
+
import { createRequire as createRequire3 } from "module";
|
|
21182
|
+
var pkg = createRequire3(import.meta.url)("../../package.json");
|
|
19891
21183
|
var EAGAIN_SLEEP = new Int32Array(new SharedArrayBuffer(4));
|
|
19892
21184
|
function writeStdout(text) {
|
|
19893
21185
|
const buf = Buffer.from(text, "utf8");
|
|
@@ -20045,7 +21337,7 @@ function readSessionInstructionSourceFile(path) {
|
|
|
20045
21337
|
if (stat.size > SESSION_MANAGED_INPUT_MAX_BYTES) {
|
|
20046
21338
|
throw new Error(`SESSION_SOURCE_INPUT_TOO_LARGE: instruction source file exceeds ${SESSION_MANAGED_INPUT_MAX_BYTES} bytes`);
|
|
20047
21339
|
}
|
|
20048
|
-
return
|
|
21340
|
+
return readFileSync18(path, "utf-8");
|
|
20049
21341
|
}
|
|
20050
21342
|
function parseLayeredReference(value) {
|
|
20051
21343
|
const trimmed = value.trim();
|
|
@@ -20074,7 +21366,7 @@ async function collectSessionSources(opts, tool, store) {
|
|
|
20074
21366
|
const path = resolveSessionPath(value);
|
|
20075
21367
|
if (!existsSync24(path))
|
|
20076
21368
|
throw new Error(`Identity instruction export not found: ${path}`);
|
|
20077
|
-
const parsed = JSON.parse(
|
|
21369
|
+
const parsed = JSON.parse(readFileSync18(path, "utf-8"));
|
|
20078
21370
|
sources.push(...sourcesFromIdentityExport(parsed, { path, tool, orderOffset: sources.length }));
|
|
20079
21371
|
}
|
|
20080
21372
|
return sources.map((source) => {
|
|
@@ -20218,7 +21510,7 @@ function readProjectContextBundleOption(value, allowMissing = false) {
|
|
|
20218
21510
|
if (stat.size > PROJECT_CONTEXT_MAX_INPUT_BYTES) {
|
|
20219
21511
|
throw new ProjectContextError("PROJECT_CONTEXT_INPUT_TOO_LARGE", `bundle exceeds ${PROJECT_CONTEXT_MAX_INPUT_BYTES} bytes`);
|
|
20220
21512
|
}
|
|
20221
|
-
return { json:
|
|
21513
|
+
return { json: readFileSync18(path, "utf8"), sourcePath: path };
|
|
20222
21514
|
}
|
|
20223
21515
|
function readBoundedProjectContextStdin() {
|
|
20224
21516
|
const chunks = [];
|
|
@@ -20382,11 +21674,11 @@ program.command("add <path>").description("Ingest a file into the config DB").op
|
|
|
20382
21674
|
console.error(chalk.red(`File not found: ${abs}`));
|
|
20383
21675
|
process.exit(1);
|
|
20384
21676
|
}
|
|
20385
|
-
const rawContent =
|
|
21677
|
+
const rawContent = readFileSync18(abs, "utf-8");
|
|
20386
21678
|
const storedFmt = detectFormat(abs);
|
|
20387
21679
|
const fmt = redactFormatForTarget(abs, storedFmt);
|
|
20388
21680
|
const { content, redacted, isTemplate: isTemplate2 } = redactContent(rawContent, fmt);
|
|
20389
|
-
const targetPath = abs.startsWith(
|
|
21681
|
+
const targetPath = abs.startsWith(homedir13()) ? abs.replace(homedir13(), "~") : abs;
|
|
20390
21682
|
const name = opts.name || filePath.split("/").pop();
|
|
20391
21683
|
const store = resolveConfigStore();
|
|
20392
21684
|
const allConfigs = await store.listConfigs();
|
|
@@ -20561,7 +21853,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
|
|
|
20561
21853
|
for (const entry of entries) {
|
|
20562
21854
|
if (!entry.isDirectory())
|
|
20563
21855
|
continue;
|
|
20564
|
-
const projDir =
|
|
21856
|
+
const projDir = join25(absDir, entry.name);
|
|
20565
21857
|
const hasAgentConfig = [
|
|
20566
21858
|
"CLAUDE.md",
|
|
20567
21859
|
".mcp.json",
|
|
@@ -20574,7 +21866,7 @@ program.command("sync").description("Sync known AI coding configs from disk into
|
|
|
20574
21866
|
".aicopilot",
|
|
20575
21867
|
".cursor",
|
|
20576
21868
|
".agents"
|
|
20577
|
-
].some((marker) => existsSync24(
|
|
21869
|
+
].some((marker) => existsSync24(join25(projDir, marker)));
|
|
20578
21870
|
if (!hasAgentConfig)
|
|
20579
21871
|
continue;
|
|
20580
21872
|
const result2 = await syncProject({ projectDir: projDir, dryRun: opts.dryRun, store });
|
|
@@ -20625,10 +21917,10 @@ program.command("import <file>").description("Import configs from a tar.gz bundl
|
|
|
20625
21917
|
});
|
|
20626
21918
|
program.command("whoami").description("Show setup summary").action(async () => {
|
|
20627
21919
|
const store = resolveConfigStore();
|
|
20628
|
-
const dbPath =
|
|
21920
|
+
const dbPath = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join25(getRawStoreRoot(), "instructions.db");
|
|
20629
21921
|
const stats = await store.getConfigStats();
|
|
20630
21922
|
console.log(chalk.bold("@hasna/instructions") + chalk.dim(" v" + pkg.version));
|
|
20631
|
-
console.log(chalk.cyan(
|
|
21923
|
+
console.log(chalk.cyan(store.mode === "api" ? "API:" : "DB:") + " " + dbPath);
|
|
20632
21924
|
console.log(chalk.cyan("Total configs:") + " " + (stats["total"] || 0));
|
|
20633
21925
|
console.log();
|
|
20634
21926
|
console.log(chalk.bold("By category:"));
|
|
@@ -21504,14 +22796,14 @@ mcpCmd.command("install").alias("add").description("Install configs MCP server i
|
|
|
21504
22796
|
} else if (target === "codex") {
|
|
21505
22797
|
const { appendFileSync, existsSync: ex } = await import("fs");
|
|
21506
22798
|
const { join: j } = await import("path");
|
|
21507
|
-
const configPath = j(
|
|
22799
|
+
const configPath = j(homedir13(), ".codex", "config.toml");
|
|
21508
22800
|
const block = `
|
|
21509
22801
|
[mcp_servers.configs]
|
|
21510
22802
|
command = "${mcpBinary}"
|
|
21511
22803
|
args = []
|
|
21512
22804
|
`;
|
|
21513
22805
|
if (ex(configPath)) {
|
|
21514
|
-
const content =
|
|
22806
|
+
const content = readFileSync18(configPath, "utf-8");
|
|
21515
22807
|
if (content.includes("[mcp_servers.configs]")) {
|
|
21516
22808
|
console.log(chalk.dim("= Already installed in Codex"));
|
|
21517
22809
|
continue;
|
|
@@ -21522,7 +22814,7 @@ args = []
|
|
|
21522
22814
|
} else if (target === "antigravity") {
|
|
21523
22815
|
const { mkdirSync: md, readFileSync: rf, writeFileSync: wf, existsSync: ex } = await import("fs");
|
|
21524
22816
|
const { dirname: dn, join: j } = await import("path");
|
|
21525
|
-
const configPath = j(
|
|
22817
|
+
const configPath = j(homedir13(), ".gemini", "config", "mcp_config.json");
|
|
21526
22818
|
let settings = {};
|
|
21527
22819
|
if (ex(configPath)) {
|
|
21528
22820
|
try {
|
|
@@ -21606,9 +22898,9 @@ DB stats:`));
|
|
|
21606
22898
|
if (count > 0)
|
|
21607
22899
|
console.log(` ${key.padEnd(18)} ${count}`);
|
|
21608
22900
|
}
|
|
21609
|
-
const location =
|
|
22901
|
+
const location = store.mode === "api" ? store.v1BaseUrl : process.env["HASNA_INSTRUCTIONS_DB_PATH"] || join25(getRawStoreRoot(), "instructions.db");
|
|
21610
22902
|
console.log(chalk.dim(`
|
|
21611
|
-
${
|
|
22903
|
+
${store.mode === "api" ? "API" : "DB"}: ${location}`));
|
|
21612
22904
|
});
|
|
21613
22905
|
program.command("status").description("Health check: total configs, drift from disk, unredacted secrets").option("--json", "output metadata-only JSON").action(async (opts) => {
|
|
21614
22906
|
const status = await getConfigsStatus(resolveConfigStore());
|
|
@@ -21667,10 +22959,10 @@ managedSkillsCmd.command("apply").option("--dry-run", "preview without writing")
|
|
|
21667
22959
|
});
|
|
21668
22960
|
program.command("backup").description("Export configs to a timestamped backup file").action(async () => {
|
|
21669
22961
|
const { mkdirSync: mk } = await import("fs");
|
|
21670
|
-
const backupDir =
|
|
22962
|
+
const backupDir = join25(getRawStoreRoot(), "backups");
|
|
21671
22963
|
mk(backupDir, { recursive: true });
|
|
21672
22964
|
const ts = new Date().toISOString().replace(/[:.]/g, "-").replace("T", "-").slice(0, 19);
|
|
21673
|
-
const outPath =
|
|
22965
|
+
const outPath = join25(backupDir, `configs-${ts}.tar.gz`);
|
|
21674
22966
|
const result = await exportConfigs(outPath, { store: resolveConfigStore() });
|
|
21675
22967
|
const { statSync: st } = await import("fs");
|
|
21676
22968
|
const size = st(outPath).size;
|
|
@@ -21858,7 +23150,7 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
21858
23150
|
continue;
|
|
21859
23151
|
const { readdirSync: readdirSync7 } = await import("fs");
|
|
21860
23152
|
for (const f of readdirSync7(absDir).filter((f2) => f2.endsWith(".md"))) {
|
|
21861
|
-
const abs =
|
|
23153
|
+
const abs = join25(absDir, f);
|
|
21862
23154
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
21863
23155
|
}
|
|
21864
23156
|
} else {
|
|
@@ -21886,7 +23178,7 @@ program.command("watch").description("Watch known config files for changes and a
|
|
|
21886
23178
|
if (!existsSync24(absDir))
|
|
21887
23179
|
continue;
|
|
21888
23180
|
for (const f of rd(absDir).filter((f2) => f2.endsWith(".md"))) {
|
|
21889
|
-
const abs =
|
|
23181
|
+
const abs = join25(absDir, f);
|
|
21890
23182
|
if (!mtimes.has(abs)) {
|
|
21891
23183
|
mtimes.set(abs, st(abs).mtimeMs);
|
|
21892
23184
|
changed++;
|
|
@@ -21926,7 +23218,7 @@ program.command("report").description("Summary of stored configs, drift, and eco
|
|
|
21926
23218
|
missing++;
|
|
21927
23219
|
continue;
|
|
21928
23220
|
}
|
|
21929
|
-
const disk =
|
|
23221
|
+
const disk = readFileSync18(abs, "utf-8");
|
|
21930
23222
|
const { content: redactedDisk } = redactContent(disk, redactFormatForTarget(c.target_path, c.format));
|
|
21931
23223
|
if (redactedDisk !== c.content)
|
|
21932
23224
|
drifted++;
|
|
@@ -22026,12 +23318,9 @@ program.command("bootstrap").description("Install the full @hasna ecosystem: CLI
|
|
|
22026
23318
|
{ name: "@hasna/skills", bin: "skills", mcp: "skills-mcp" },
|
|
22027
23319
|
{ name: "@hasna/economy", bin: "economy", mcp: "economy-mcp" },
|
|
22028
23320
|
{ name: "@hasna/attachments", bin: "attachments", mcp: "attachments-mcp" },
|
|
22029
|
-
{ name: "@hasna/sessions", bin: "sessions", mcp: "sessions-mcp" },
|
|
22030
23321
|
{ name: "@hasna/emails", bin: "emails", mcp: "emails-mcp" },
|
|
22031
23322
|
{ name: "@hasna/recordings", bin: "recordings", mcp: "recordings-mcp" },
|
|
22032
|
-
{ name: "@hasna/
|
|
22033
|
-
{ name: "@hasna/assistants", bin: "assistants", mcp: "assistants-mcp" },
|
|
22034
|
-
{ name: "@hasna/brains", bin: "brains", mcp: "brains-mcp" }
|
|
23323
|
+
{ name: "@hasna/assistants", bin: "assistants", mcp: "assistants-mcp" }
|
|
22035
23324
|
];
|
|
22036
23325
|
console.log(chalk.bold("@hasna/instructions bootstrap") + chalk.dim(` \u2014 installing ${packages.length} ecosystem packages
|
|
22037
23326
|
`));
|
|
@@ -22139,13 +23428,13 @@ providerContextCmd.command("resolve").description("Resolve the endpoint to a pro
|
|
|
22139
23428
|
try {
|
|
22140
23429
|
const rawEndpoint = opts.endpoint ?? process.env["ANTHROPIC_BASE_URL"] ?? "";
|
|
22141
23430
|
const rawModel = opts.model ?? process.env["ANTHROPIC_MODEL"] ?? "";
|
|
22142
|
-
const
|
|
23431
|
+
const homeDir6 = opts.home ?? homedir13();
|
|
22143
23432
|
const origin = normalizeEndpointOrigin(rawEndpoint);
|
|
22144
23433
|
const resolution = resolveAndRenderProviderContext({
|
|
22145
23434
|
origin,
|
|
22146
23435
|
rawEndpoint,
|
|
22147
23436
|
rawModel,
|
|
22148
|
-
homeDir:
|
|
23437
|
+
homeDir: homeDir6
|
|
22149
23438
|
});
|
|
22150
23439
|
if (opts.json) {
|
|
22151
23440
|
printJson({
|