@harnessforce/cli 0.2.0 → 0.4.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 +39 -11
- package/dist/bin.js +824 -465
- package/package.json +5 -5
package/dist/bin.js
CHANGED
|
@@ -3,15 +3,15 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import { constants, homedir, tmpdir } from "node:os";
|
|
4
4
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
5
5
|
import { basename, dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
|
|
6
|
-
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
7
|
-
import { access, chmod, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, utimes, writeFile } from "node:fs/promises";
|
|
6
|
+
import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
7
|
+
import { access, chmod, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, utimes, writeFile } from "node:fs/promises";
|
|
8
8
|
import { execFile, spawn } from "node:child_process";
|
|
9
9
|
import { constants as constants$1, createReadStream } from "node:fs";
|
|
10
10
|
import { createServer } from "node:http";
|
|
11
|
-
import { ATTR, INTERVENTION_KINDS, LOOP_KINDS, PROPOSAL_KINDS, isToken, normalizeRepository } from "@harnessforce/semconv";
|
|
11
|
+
import { ATTR, INTERVENTION_KINDS, LOOP_KINDS, MAX_WORK_LOCATIONS, PROPOSAL_KINDS, isToken, normalizeRepository } from "@harnessforce/semconv";
|
|
12
12
|
import { createInterface } from "node:readline";
|
|
13
13
|
import { isDeepStrictEqual } from "node:util";
|
|
14
|
-
|
|
14
|
+
// ../agent-core/src/config/canonical.ts
|
|
15
15
|
const byCodeUnit$1 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
16
16
|
function canonicalJson(value) {
|
|
17
17
|
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
@@ -28,11 +28,9 @@ function sortComponents(components) {
|
|
|
28
28
|
return [...components].sort((a, b) => byCodeUnit$1(a.kind, b.kind) || byCodeUnit$1(a.source, b.source) || byCodeUnit$1(a.id, b.id));
|
|
29
29
|
}
|
|
30
30
|
const snapshotId = (components) => hashValue(sortComponents(components));
|
|
31
|
-
|
|
32
|
-
//#region ../agent-core/src/object.ts
|
|
31
|
+
// ../agent-core/src/object.ts
|
|
33
32
|
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
34
|
-
|
|
35
|
-
//#region ../agent-core/src/config/files.ts
|
|
33
|
+
// ../agent-core/src/config/files.ts
|
|
36
34
|
const MAX_DEPTH = 8;
|
|
37
35
|
const MAX_FILE_BYTES = 1048576;
|
|
38
36
|
var CollectionExpired = class extends Error {};
|
|
@@ -93,8 +91,7 @@ async function listDirectories(dir, guard) {
|
|
|
93
91
|
for (const entry of await entries(dir, guard)) if (await kindOf(dir, entry, guard) === "directory") names.push(entry.name);
|
|
94
92
|
return names;
|
|
95
93
|
}
|
|
96
|
-
|
|
97
|
-
//#region ../agent-core/src/managed.ts
|
|
94
|
+
// ../agent-core/src/managed.ts
|
|
98
95
|
const MANAGED_DIRS = {
|
|
99
96
|
darwin: "/Library/Application Support/ClaudeCode",
|
|
100
97
|
win32: "C:\\Program Files\\ClaudeCode"
|
|
@@ -113,8 +110,7 @@ async function readManagedEnv(managedDir, names) {
|
|
|
113
110
|
}
|
|
114
111
|
return values;
|
|
115
112
|
}
|
|
116
|
-
|
|
117
|
-
//#region ../agent-core/src/process/lookup.ts
|
|
113
|
+
// ../agent-core/src/process/lookup.ts
|
|
118
114
|
const DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD";
|
|
119
115
|
const NPM_SHIM_MAX_BYTES = 65536;
|
|
120
116
|
const SHEBANG_MAX_BYTES = 256;
|
|
@@ -295,8 +291,7 @@ async function commandLineFor(file, args, context, { quoteArgs = true } = {}) {
|
|
|
295
291
|
verbatim: true
|
|
296
292
|
};
|
|
297
293
|
}
|
|
298
|
-
|
|
299
|
-
//#region ../agent-core/src/process/git.ts
|
|
294
|
+
// ../agent-core/src/process/git.ts
|
|
300
295
|
const GIT_TIMEOUT_MS$1 = 1e3;
|
|
301
296
|
const execGit = (file, args, env, timeoutMs) => new Promise((resolve) => {
|
|
302
297
|
execFile(file, args, {
|
|
@@ -334,8 +329,7 @@ function createGitRunner({ platform, env, processCwd, excludeTarget, timeoutMs =
|
|
|
334
329
|
], gitEnv, timeoutMs);
|
|
335
330
|
};
|
|
336
331
|
}
|
|
337
|
-
|
|
338
|
-
//#region ../agent-core/src/process/runtime-env.ts
|
|
332
|
+
// ../agent-core/src/process/runtime-env.ts
|
|
339
333
|
const RUNTIME_VARIABLES = [
|
|
340
334
|
"NODE_TLS_REJECT_UNAUTHORIZED",
|
|
341
335
|
"NODE_EXTRA_CA_CERTS",
|
|
@@ -412,8 +406,7 @@ function takeStashedRuntimeEnv(env, platform) {
|
|
|
412
406
|
if (!isObject(value)) return {};
|
|
413
407
|
return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string" && isRuntimeVariable(entry[0], platform)));
|
|
414
408
|
}
|
|
415
|
-
|
|
416
|
-
//#region src/shared/messages.ts
|
|
409
|
+
// src/shared/messages.ts
|
|
417
410
|
const INIT_MESSAGES = {
|
|
418
411
|
success: "Connected to Harnessforce. Restart Claude Code to apply the settings",
|
|
419
412
|
pluginAutoUpdateOn: "Automatic updates for the Harnessforce plugin are on. To turn them off, run `harnessforce init --no-plugin-auto-update` or use /plugin",
|
|
@@ -432,25 +425,32 @@ const INIT_MESSAGES = {
|
|
|
432
425
|
noWorkspace: "You are not a member of any Workspace. Create a Workspace or accept an invitation, then run `harnessforce init` again",
|
|
433
426
|
loginFailed: "The login failed. Run `harnessforce init` again",
|
|
434
427
|
viewer: "The view-only role cannot issue an ingest key. Contact a Workspace administrator",
|
|
435
|
-
limitOwnerAdmin: "The number of active ingest keys has reached the limit.
|
|
428
|
+
limitOwnerAdmin: "The number of active ingest keys has reached the limit. Ask members to revoke unused keys in My ingest keys on their account page",
|
|
436
429
|
limitMember: "The ingest key limit has been reached. Contact a Workspace administrator",
|
|
437
430
|
gateUnavailable: "Could not apply the change on the receiving side. Nothing was changed. Try again",
|
|
438
431
|
ingestUnavailable: "This environment does not receive telemetry, so an ingest key cannot be issued",
|
|
439
432
|
saveFailed: "Could not save the ingest key. Run `harnessforce init` again",
|
|
440
433
|
codexSaveFailed: "Could not write the Codex configuration. Run `harnessforce init` again",
|
|
441
|
-
|
|
442
|
-
|
|
434
|
+
contentNotStored: "This workspace does not store content, so sending content was not enabled. An owner or admin can turn it on under Collection & retention in the workspace settings, then run `harnessforce init --send-content` again",
|
|
435
|
+
contentNotSent: "Content is not sent from this machine. Run `harnessforce init --send-content` to send it"
|
|
443
436
|
};
|
|
444
437
|
const codexNotes = (configPath) => [
|
|
445
438
|
`Wrote the Codex configuration (${configPath}). Restart Codex to apply it`,
|
|
446
|
-
"Codex hooks do not run until you trust them in Codex's /hooks. Trust the harnessforce SessionStart
|
|
439
|
+
"Codex hooks do not run until you trust them in Codex's /hooks. Trust the harnessforce SessionStart, PostToolUse, and Stop hooks in /hooks",
|
|
447
440
|
"To use the Harnessforce MCP server, log in with `codex mcp login harnessforce`"
|
|
448
441
|
].join("\n");
|
|
449
442
|
const nonDefaultConnectionMessage = (url) => `Connecting to ${url} (not the default production endpoint)`;
|
|
450
443
|
const listenFailedMessage = (port) => `${INIT_MESSAGES.listenFailed}. Stop the process using port ${port}, or specify another port with \`harnessforce init --port\``;
|
|
451
444
|
const keychainUnavailableMessage = (keychain, message = INIT_MESSAGES.keychainUnavailable) => keychain.unavailableHint ? `${message}. ${keychain.unavailableHint}` : message;
|
|
452
|
-
|
|
453
|
-
|
|
445
|
+
const CONTENT_READERS = {
|
|
446
|
+
all_members: "Everyone in the workspace can read it",
|
|
447
|
+
owners_admins_and_sender: "Owners, admins and you can read it",
|
|
448
|
+
sender_only: "Within the workspace, only you can read it"
|
|
449
|
+
};
|
|
450
|
+
function contentEnabledMessage(content) {
|
|
451
|
+
return `Sending ${[...content.isPromptsStored ? ["the prompts you type"] : [], ...content.isModelAndToolsStored ? ["model responses and tool input and output"] : []].join(", ")} is now enabled. Values that match known patterns for secrets and personal information are masked, but not everything can be detected. ${CONTENT_READERS[content.visibility]}. It is deleted after ${content.retentionDays} days`;
|
|
452
|
+
}
|
|
453
|
+
// src/credentials/keychain.ts
|
|
454
454
|
async function openSingleStore(store) {
|
|
455
455
|
if (!await store.isAvailable()) return void 0;
|
|
456
456
|
return {
|
|
@@ -476,8 +476,7 @@ const workspaceAccounts = (workspaceId) => [
|
|
|
476
476
|
];
|
|
477
477
|
const isIngestKeyAccount = (account) => account.endsWith(INGEST_KEY_SUFFIX);
|
|
478
478
|
const apiTokenWorkspaceId = (account) => account.endsWith(API_TOKEN_SUFFIX) ? account.slice(0, -10) : void 0;
|
|
479
|
-
|
|
480
|
-
//#region src/credentials/linux-keychain.ts
|
|
479
|
+
// src/credentials/linux-keychain.ts
|
|
481
480
|
const SECRET_SERVICE_TIMEOUT_MS = 6e4;
|
|
482
481
|
const WAITING_NOTICE_DELAY_MS = 2e3;
|
|
483
482
|
const PROBE_ACCOUNT$1 = "availability-probe";
|
|
@@ -631,8 +630,7 @@ async function migrate({ kernel, secretService }, workspaceId) {
|
|
|
631
630
|
clearTimeout(timer);
|
|
632
631
|
}
|
|
633
632
|
}
|
|
634
|
-
|
|
635
|
-
//#region src/credentials/os-keychain.ts
|
|
633
|
+
// src/credentials/os-keychain.ts
|
|
636
634
|
const PROBE_ACCOUNT = "availability-probe";
|
|
637
635
|
function createOsKeychain({ platform, load, secretService }) {
|
|
638
636
|
let loaded;
|
|
@@ -714,8 +712,7 @@ const unsupported = {
|
|
|
714
712
|
withTimeout: () => unsupported,
|
|
715
713
|
openForInit: async () => void 0
|
|
716
714
|
};
|
|
717
|
-
|
|
718
|
-
//#region src/credentials/secret-service-process.ts
|
|
715
|
+
// src/credentials/secret-service-process.ts
|
|
719
716
|
const CHILD_SCRIPT = `
|
|
720
717
|
const [op, service, account, timeoutMs, modulePath] = process.argv.slice(1);
|
|
721
718
|
setTimeout(() => process.kill(process.pid, "SIGKILL"), Number(timeoutMs));
|
|
@@ -790,8 +787,7 @@ function createSecretServiceProcess({ execPath, resolveKeyring }) {
|
|
|
790
787
|
}
|
|
791
788
|
};
|
|
792
789
|
}
|
|
793
|
-
|
|
794
|
-
//#region src/import/repository.ts
|
|
790
|
+
// src/import/repository.ts
|
|
795
791
|
const GIT_TIMEOUT_MS = 5e3;
|
|
796
792
|
const createImportGit = (platform, env, processCwd) => createGitRunner({
|
|
797
793
|
platform,
|
|
@@ -800,8 +796,7 @@ const createImportGit = (platform, env, processCwd) => createGitRunner({
|
|
|
800
796
|
excludeTarget: false,
|
|
801
797
|
timeoutMs: GIT_TIMEOUT_MS
|
|
802
798
|
});
|
|
803
|
-
|
|
804
|
-
//#region src/init/browser.ts
|
|
799
|
+
// src/init/browser.ts
|
|
805
800
|
const STILL_RUNNING_MS = 3e3;
|
|
806
801
|
function openerFor(platform, url) {
|
|
807
802
|
if (platform === "darwin") return ["open", [url]];
|
|
@@ -838,35 +833,63 @@ async function openBrowser(url, { platform, env, cwd, restoredEnv, spawn: spawnI
|
|
|
838
833
|
child.once("exit", (code) => finish(code === 0));
|
|
839
834
|
});
|
|
840
835
|
}
|
|
841
|
-
|
|
842
|
-
//#region src/init/loopback.ts
|
|
836
|
+
// src/init/loopback.ts
|
|
843
837
|
const HOST = "127.0.0.1";
|
|
844
838
|
const CALLBACK_PATH = "/callback";
|
|
845
|
-
const
|
|
846
|
-
|
|
847
|
-
const
|
|
839
|
+
const COMPLETION_PATH = "/cli/connected";
|
|
840
|
+
function completionUrl(webOrigin, completion) {
|
|
841
|
+
const url = new URL(COMPLETION_PATH, webOrigin);
|
|
842
|
+
url.searchParams.set("status", completion.status);
|
|
843
|
+
if (completion.status === "connected" && completion.workspaceId) url.searchParams.set("workspace", completion.workspaceId);
|
|
844
|
+
return url.href;
|
|
845
|
+
}
|
|
846
|
+
function parseRequestUrl(target) {
|
|
847
|
+
try {
|
|
848
|
+
return new URL(target ?? "/", `http://${HOST}`);
|
|
849
|
+
} catch {
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
function isSameState(received, expected) {
|
|
854
|
+
if (received === null) return false;
|
|
855
|
+
const receivedBytes = Buffer.from(received, "utf8");
|
|
856
|
+
const expectedBytes = Buffer.from(expected, "utf8");
|
|
857
|
+
return receivedBytes.length === expectedBytes.length && timingSafeEqual(receivedBytes, expectedBytes);
|
|
858
|
+
}
|
|
848
859
|
async function startLoopback(options) {
|
|
849
860
|
let settle = () => {};
|
|
850
861
|
const callback = new Promise((resolve) => {
|
|
851
862
|
settle = resolve;
|
|
852
863
|
});
|
|
864
|
+
let held;
|
|
853
865
|
const server = createServer((req, res) => {
|
|
854
|
-
const url =
|
|
866
|
+
const url = parseRequestUrl(req.url);
|
|
867
|
+
if (url === void 0) {
|
|
868
|
+
res.writeHead(400).end();
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
855
871
|
if (url.pathname !== CALLBACK_PATH) {
|
|
856
872
|
res.writeHead(404).end();
|
|
857
873
|
return;
|
|
858
874
|
}
|
|
859
|
-
|
|
875
|
+
if (isStopped) {
|
|
876
|
+
res.writeHead(404).end();
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
if (!isSameState(url.searchParams.get("state"), options.state)) {
|
|
880
|
+
res.writeHead(303, {
|
|
881
|
+
location: completionUrl(options.webOrigin, { status: "failed" }),
|
|
882
|
+
"cache-control": "no-store"
|
|
883
|
+
});
|
|
884
|
+
res.end();
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
held = res;
|
|
888
|
+
stop({
|
|
860
889
|
code: url.searchParams.get("code") || void 0,
|
|
861
890
|
state: url.searchParams.get("state") ?? void 0,
|
|
862
891
|
error: url.searchParams.get("error") ?? void 0
|
|
863
|
-
};
|
|
864
|
-
const isSuccess = !received.error && Boolean(received.code) && received.state === options.state;
|
|
865
|
-
res.writeHead(200, {
|
|
866
|
-
"content-type": "text/html; charset=utf-8",
|
|
867
|
-
"cache-control": "no-store"
|
|
868
892
|
});
|
|
869
|
-
res.end(isSuccess ? SUCCESS_PAGE : FAILURE_PAGE, () => stop(received));
|
|
870
893
|
});
|
|
871
894
|
const timer = setTimeout(() => stop(void 0), options.timeoutMs);
|
|
872
895
|
let isStopped = false;
|
|
@@ -875,9 +898,19 @@ async function startLoopback(options) {
|
|
|
875
898
|
isStopped = true;
|
|
876
899
|
clearTimeout(timer);
|
|
877
900
|
server.close();
|
|
878
|
-
server.closeAllConnections();
|
|
901
|
+
if (!held) server.closeAllConnections();
|
|
879
902
|
settle(received);
|
|
880
903
|
}
|
|
904
|
+
function finish(completion) {
|
|
905
|
+
const response = held;
|
|
906
|
+
held = void 0;
|
|
907
|
+
if (!response) return;
|
|
908
|
+
response.writeHead(303, {
|
|
909
|
+
location: completionUrl(options.webOrigin, completion),
|
|
910
|
+
"cache-control": "no-store"
|
|
911
|
+
});
|
|
912
|
+
response.end(() => server.closeAllConnections());
|
|
913
|
+
}
|
|
881
914
|
await new Promise((resolve, reject) => {
|
|
882
915
|
server.once("error", reject);
|
|
883
916
|
server.listen(options.port, HOST, resolve);
|
|
@@ -888,11 +921,11 @@ async function startLoopback(options) {
|
|
|
888
921
|
const { port } = server.address();
|
|
889
922
|
return {
|
|
890
923
|
redirectUri: `http://${HOST}:${port}${CALLBACK_PATH}`,
|
|
891
|
-
callback
|
|
924
|
+
callback,
|
|
925
|
+
finish
|
|
892
926
|
};
|
|
893
927
|
}
|
|
894
|
-
|
|
895
|
-
//#region ../agent-core/src/url.ts
|
|
928
|
+
// ../agent-core/src/url.ts
|
|
896
929
|
const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
|
|
897
930
|
"localhost",
|
|
898
931
|
"127.0.0.1",
|
|
@@ -914,8 +947,7 @@ function underBase(base, path) {
|
|
|
914
947
|
return url;
|
|
915
948
|
}
|
|
916
949
|
const withoutExtras = (url) => `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
|
|
917
|
-
|
|
918
|
-
//#region ../agent-core/src/client-update.ts
|
|
950
|
+
// ../agent-core/src/client-update.ts
|
|
919
951
|
const CLIENT_UPDATE_HEADER = "harnessforce-client-update";
|
|
920
952
|
const UPDATE_COMMANDS = {
|
|
921
953
|
cli: "npm install -g @harnessforce/cli@latest",
|
|
@@ -1074,8 +1106,7 @@ async function recheckClientStatus(options) {
|
|
|
1074
1106
|
}
|
|
1075
1107
|
return await readUpdateState(options.statePath, options.version) ?? state;
|
|
1076
1108
|
}
|
|
1077
|
-
|
|
1078
|
-
//#region ../agent-core/src/ingest.ts
|
|
1109
|
+
// ../agent-core/src/ingest.ts
|
|
1079
1110
|
const SEND_TIMEOUT_MS = 2e3;
|
|
1080
1111
|
function ingestBaseFrom(endpoint) {
|
|
1081
1112
|
const base = parseAllowedUrl(endpoint);
|
|
@@ -1110,8 +1141,7 @@ async function postItems(destination, path, items, fetchImpl, timeoutMs = SEND_T
|
|
|
1110
1141
|
};
|
|
1111
1142
|
}
|
|
1112
1143
|
}
|
|
1113
|
-
|
|
1114
|
-
//#region ../agent-core/src/config/hash-input.ts
|
|
1144
|
+
// ../agent-core/src/config/hash-input.ts
|
|
1115
1145
|
function withoutSecrets(entry) {
|
|
1116
1146
|
const { env, headers, url, args: _args, ...rest } = entry;
|
|
1117
1147
|
const result = rest;
|
|
@@ -1143,8 +1173,7 @@ function hashInput(kind, value) {
|
|
|
1143
1173
|
if (kind === "hook" && Array.isArray(value)) return value.map(reduceHookGroup);
|
|
1144
1174
|
return value;
|
|
1145
1175
|
}
|
|
1146
|
-
|
|
1147
|
-
//#region ../agent-core/src/config/scope.ts
|
|
1176
|
+
// ../agent-core/src/config/scope.ts
|
|
1148
1177
|
const MAX_COMPONENTS = 1e3;
|
|
1149
1178
|
var TooManyComponents = class extends Error {};
|
|
1150
1179
|
var Sink = class {
|
|
@@ -1200,11 +1229,9 @@ function collectMcpServers$1(s, servers) {
|
|
|
1200
1229
|
if (!isObject(servers)) return;
|
|
1201
1230
|
for (const [name, value] of Object.entries(servers)) if (isObject(value)) s.addValue("mcp_server", name, value);
|
|
1202
1231
|
}
|
|
1203
|
-
|
|
1204
|
-
//#region ../agent-core/src/plugin-auto-update.ts
|
|
1232
|
+
// ../agent-core/src/plugin-auto-update.ts
|
|
1205
1233
|
const MARKETPLACE_NAME = "harnessforce-agent";
|
|
1206
|
-
|
|
1207
|
-
//#region src/shared/settings.ts
|
|
1234
|
+
// src/shared/settings.ts
|
|
1208
1235
|
const OTEL_HEADERS_HELPER = "harnessforce otel-headers";
|
|
1209
1236
|
const PLUGIN_ID$1 = "harnessforce@harnessforce-agent";
|
|
1210
1237
|
const NEW_FILE_MODE = 384;
|
|
@@ -1292,8 +1319,7 @@ async function writeUserSettings(path, settings) {
|
|
|
1292
1319
|
await rm(temporary, { force: true });
|
|
1293
1320
|
}
|
|
1294
1321
|
}
|
|
1295
|
-
|
|
1296
|
-
//#region src/destinations.ts
|
|
1322
|
+
// src/destinations.ts
|
|
1297
1323
|
const NAMES = [
|
|
1298
1324
|
"HARNESSFORCE_URL",
|
|
1299
1325
|
"HARNESSFORCE_ENDPOINT",
|
|
@@ -1314,8 +1340,7 @@ async function resolveCliDestinations(env, settingsPath, defaultUrl) {
|
|
|
1314
1340
|
readApiUrl: pick("HARNESSFORCE_URL") ?? defaultUrl
|
|
1315
1341
|
};
|
|
1316
1342
|
}
|
|
1317
|
-
|
|
1318
|
-
//#region src/client-update.ts
|
|
1343
|
+
// src/client-update.ts
|
|
1319
1344
|
const updateStatePath = (homeDir) => join(homeDir, ".harnessforce", "update-status.json");
|
|
1320
1345
|
const readCliUpdateState = (homeDir, version) => readUpdateState(updateStatePath(homeDir), version);
|
|
1321
1346
|
/**
|
|
@@ -1348,8 +1373,7 @@ async function noticeUpdate(deps) {
|
|
|
1348
1373
|
});
|
|
1349
1374
|
if (line) deps.stderr(`${line}\n`);
|
|
1350
1375
|
}
|
|
1351
|
-
|
|
1352
|
-
//#region src/help.ts
|
|
1376
|
+
// src/help.ts
|
|
1353
1377
|
const SENDS_NOTHING_LOCAL = "Nothing. It reads local files only and makes no network request.";
|
|
1354
1378
|
const COMMANDS$1 = {
|
|
1355
1379
|
init: {
|
|
@@ -1366,8 +1390,9 @@ What it does:
|
|
|
1366
1390
|
|
|
1367
1391
|
Options:
|
|
1368
1392
|
--port <port> Loopback port for the login callback, 1-65535 (default 8080).
|
|
1369
|
-
--send-content Also send the
|
|
1370
|
-
|
|
1393
|
+
--send-content Also send the prompts you type, model responses and tool
|
|
1394
|
+
input and output. Takes effect only for the categories the
|
|
1395
|
+
Workspace stores. Without it, init turns all of them off.
|
|
1371
1396
|
--no-plugin-auto-update
|
|
1372
1397
|
Turn off automatic updates of the Harnessforce plugin in
|
|
1373
1398
|
Claude Code. On by default; a value you set is kept.
|
|
@@ -1377,9 +1402,9 @@ Sends:
|
|
|
1377
1402
|
telemetry (token counts, tool names, timings) with that key.
|
|
1378
1403
|
|
|
1379
1404
|
Does not send:
|
|
1380
|
-
Prompt text
|
|
1381
|
-
|
|
1382
|
-
plain file.
|
|
1405
|
+
Prompt text, model responses and tool input or output (unless --send-content
|
|
1406
|
+
and the Workspace stores that category), code diffs, and secrets. The key is
|
|
1407
|
+
never written to a plain file.
|
|
1383
1408
|
|
|
1384
1409
|
Exit codes:
|
|
1385
1410
|
0 Connected and configured.
|
|
@@ -1420,6 +1445,38 @@ Exit codes:
|
|
|
1420
1445
|
|
|
1421
1446
|
Examples:
|
|
1422
1447
|
harnessforce import
|
|
1448
|
+
`
|
|
1449
|
+
},
|
|
1450
|
+
status: {
|
|
1451
|
+
summary: "Show the Workspace setup steps that remain",
|
|
1452
|
+
text: `Usage: harnessforce status [--json]
|
|
1453
|
+
|
|
1454
|
+
When to use:
|
|
1455
|
+
After init, and any time you want to see which setup steps of the connected
|
|
1456
|
+
Workspace remain and whether this repository is connected to the GitHub App.
|
|
1457
|
+
|
|
1458
|
+
What it does:
|
|
1459
|
+
Reads the setup checklist of the connected Workspace and the list of
|
|
1460
|
+
connected repositories, and prints your role, each step (done, to do with its
|
|
1461
|
+
URL, or "?" when it cannot be read), and whether the repository of the current
|
|
1462
|
+
directory (its origin remote) is connected.
|
|
1463
|
+
|
|
1464
|
+
Options:
|
|
1465
|
+
--json Print the checklist and the repository as one JSON object.
|
|
1466
|
+
|
|
1467
|
+
Sends:
|
|
1468
|
+
Read requests with the API token. It writes nothing.
|
|
1469
|
+
|
|
1470
|
+
Does not send:
|
|
1471
|
+
The URL of the origin remote: it is compared on this machine.
|
|
1472
|
+
|
|
1473
|
+
Exit codes:
|
|
1474
|
+
0 The status was read, whether or not steps remain.
|
|
1475
|
+
1 Not logged in, the login expired, or communication failed.
|
|
1476
|
+
|
|
1477
|
+
Examples:
|
|
1478
|
+
harnessforce status
|
|
1479
|
+
harnessforce status --json
|
|
1423
1480
|
`
|
|
1424
1481
|
},
|
|
1425
1482
|
"otel-headers": {
|
|
@@ -1469,8 +1526,8 @@ Sends:
|
|
|
1469
1526
|
The same telemetry as an agent started directly, plus the Issue identifier.
|
|
1470
1527
|
|
|
1471
1528
|
Does not send:
|
|
1472
|
-
Anything beyond what init configured. Prompt text
|
|
1473
|
-
--send-content at init.
|
|
1529
|
+
Anything beyond what init configured. Prompt text, responses and tool input
|
|
1530
|
+
and output are sent only if you chose --send-content at init.
|
|
1474
1531
|
|
|
1475
1532
|
Exit codes:
|
|
1476
1533
|
The agent's own exit code when it ran.
|
|
@@ -1484,6 +1541,8 @@ Examples:
|
|
|
1484
1541
|
tune: {
|
|
1485
1542
|
summary: "Analyze your past sessions and report counts",
|
|
1486
1543
|
text: `Usage: harnessforce tune [--all] [--no-send] [--show-report] [--json]
|
|
1544
|
+
harnessforce tune --report-only [--all]
|
|
1545
|
+
harnessforce tune --print-schedule
|
|
1487
1546
|
harnessforce tune record
|
|
1488
1547
|
harnessforce tune --purge
|
|
1489
1548
|
|
|
@@ -1503,6 +1562,11 @@ Options:
|
|
|
1503
1562
|
--show-report Print the exact values that would be sent.
|
|
1504
1563
|
--json Print the result as one JSON object on stdout. Messages go to
|
|
1505
1564
|
stderr.
|
|
1565
|
+
--report-only Analyze and send, writing nothing to stdout (for your OS
|
|
1566
|
+
scheduler). Combines with --all only.
|
|
1567
|
+
--print-schedule
|
|
1568
|
+
Print a cron line that runs --report-only every Monday at
|
|
1569
|
+
9:00. It changes nothing and sends nothing.
|
|
1506
1570
|
record Read one proposal as JSON from stdin and store it locally.
|
|
1507
1571
|
--purge Delete the analysis, proposals, and unsent results.
|
|
1508
1572
|
|
|
@@ -1524,6 +1588,7 @@ Exit codes:
|
|
|
1524
1588
|
Examples:
|
|
1525
1589
|
harnessforce tune
|
|
1526
1590
|
harnessforce tune --no-send --json
|
|
1591
|
+
harnessforce tune --print-schedule
|
|
1527
1592
|
harnessforce tune --purge
|
|
1528
1593
|
`
|
|
1529
1594
|
},
|
|
@@ -1561,20 +1626,26 @@ Examples:
|
|
|
1561
1626
|
`
|
|
1562
1627
|
},
|
|
1563
1628
|
hook: {
|
|
1564
|
-
summary: "Session
|
|
1565
|
-
text: `Usage: harnessforce hook session-start
|
|
1629
|
+
summary: "Session hooks (used by Codex)",
|
|
1630
|
+
text: `Usage: harnessforce hook session-start|post-tool-use|stop
|
|
1566
1631
|
|
|
1567
1632
|
When to use:
|
|
1568
|
-
You normally never run it. Codex runs
|
|
1633
|
+
You normally never run it. Codex runs session-start when a session starts,
|
|
1634
|
+
post-tool-use after it edits files with apply_patch, and stop at the end of
|
|
1635
|
+
each turn.
|
|
1569
1636
|
|
|
1570
1637
|
What it does:
|
|
1571
|
-
|
|
1572
|
-
prints context for the agent (the session ID, plus a one-line
|
|
1573
|
-
registration failed).
|
|
1638
|
+
session-start reads the hook input from stdin, registers the session with the
|
|
1639
|
+
Workspace, and prints context for the agent (the session ID, plus a one-line
|
|
1640
|
+
warning if registration failed).
|
|
1641
|
+
post-tool-use and stop record the repository and branch of the Git working
|
|
1642
|
+
trees the session worked in, and stop sends them when they changed.
|
|
1574
1643
|
|
|
1575
1644
|
Sends:
|
|
1576
1645
|
Session registration: agent, session ID, repository, branch, commit, and
|
|
1577
1646
|
start time.
|
|
1647
|
+
Work locations: agent, session ID, and the repository and branch of each
|
|
1648
|
+
working tree. Paths, commits, and file names are not sent.
|
|
1578
1649
|
|
|
1579
1650
|
Does not send:
|
|
1580
1651
|
Prompt text, model responses, tool input or output, and code diffs.
|
|
@@ -1592,6 +1663,7 @@ Examples:
|
|
|
1592
1663
|
const ORDER = [
|
|
1593
1664
|
"init",
|
|
1594
1665
|
"import",
|
|
1666
|
+
"status",
|
|
1595
1667
|
"run",
|
|
1596
1668
|
"tune",
|
|
1597
1669
|
"top",
|
|
@@ -1631,8 +1703,7 @@ function resolveHelp(argv) {
|
|
|
1631
1703
|
const separator = rest.indexOf("--");
|
|
1632
1704
|
return (separator === -1 ? rest : rest.slice(0, separator)).some(isHelpFlag) ? COMMANDS$1[command].text : void 0;
|
|
1633
1705
|
}
|
|
1634
|
-
|
|
1635
|
-
//#region ../agent-core/src/hook-warning.ts
|
|
1706
|
+
// ../agent-core/src/hook-warning.ts
|
|
1636
1707
|
const RESTART_FAILED_WARNING = "Could not restart the Harnessforce hook";
|
|
1637
1708
|
const INVALID_ENDPOINT_WARNING = "The Harnessforce destination is invalid. Run `harnessforce init` again";
|
|
1638
1709
|
const notConfiguredWarning = (agent) => agent === "claude_code" ? "Harnessforce sending is not configured. Run `/harnessforce:setup`" : "Harnessforce sending is not configured. Run `harnessforce init`";
|
|
@@ -1643,21 +1714,17 @@ const DESTINATION_MISMATCH_WARNING = {
|
|
|
1643
1714
|
claude_code: "Telemetry is not being sent because the Harnessforce destination differs from the one saved by `harnessforce init`. Check this repository's settings or run `harnessforce init` again",
|
|
1644
1715
|
codex: "Sessions are not being registered because the Harnessforce destination differs from the one saved by `harnessforce init`. Run `harnessforce init` again"
|
|
1645
1716
|
};
|
|
1646
|
-
|
|
1647
|
-
//#region ../agent-core/src/issue.ts
|
|
1717
|
+
// ../agent-core/src/issue.ts
|
|
1648
1718
|
const isIssueIdentifier = (value) => isToken(value, 300);
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
//#endregion
|
|
1653
|
-
//#region ../agent-core/src/session-context.ts
|
|
1719
|
+
// ../agent-core/src/using-harnessforce.ts
|
|
1720
|
+
const usingHarnessforceBody = "---\nname: using-harnessforce\ndescription: Use when starting a session in an environment with the Harnessforce hook, when starting work on an Issue, or when asked what is recorded or collected. The hook injects this text into the context at session start.\n---\n\n# Using Harnessforce\n\nThis session has the Harnessforce hook installed. Harnessforce links the work of people and AI to Issues and records it. Follow these rules.\n\n## What is recorded\n\n- Session registration: agent, session ID, repository, branch, commit, start time, and the first prompt ID.\n- Work locations: the repository and branch of each Git working tree the session works in. Paths are not sent.\n- Config snapshot: the structure of the configuration in use (Claude Code only).\n- OTel telemetry: what the user chooses to send.\n- For work on an Issue, `record-run` registers the session as a Run and closes it. It records no Plan, Decision, or Definition of Done.\n\n## What is not collected\n\n- Prompts, responses, tool input and output, and the body of code diffs arrive only when the user sends them (`--send-content` or managed settings) and the Workspace stores that category. The members the Workspace chooses can read them. `harnessforce import` never sends them.\n- Never put these contents, secrets, or environment variable values in MCP tool input or in a record.\n\n## Which hook sends what\n\n- SessionStart: session registration and the config snapshot (Codex sends session registration only).\n- UserPromptSubmit: resends on the first prompt (Claude Code only).\n- Stop: sends the work locations at the end of a turn in which they changed.\n- A failing hook never stops the session. When the hook cannot send, it shows a one-line warning. Tell the user when a warning appears.\n\n## Which skill and tool to use, and when\n\n- For work on a specified Issue ID, or when the environment variable `HARNESSFORCE_ISSUE` is set, follow `record-run` and use `start_run`, `get_issue`, and `complete_run`. An agent that cannot load the skill calls the tools in the same order.\n- Pass the value of the `harnessforce session_id:` line in the context to `start_run`. If there are several, use the last one. If the line is missing, do not guess the session ID, and do not call `start_run` or `complete_run`.\n- Use `propose-improvements` for the analysis of `/harnessforce:tune`.\n- When no Issue is specified, do not start recording a Run.\n\n## Red Flags\n\nStop when one of these thoughts comes up.\n\n| Thought | Reality |\n| --- | --- |\n| An Issue was specified, but the work is small, so recording is unnecessary | Work on a specified Issue is recorded regardless of its size |\n| The session ID line is missing, so I will guess it and call `start_run` | Do not guess. The Run would no longer be linked to the session |\n| Summarizing the content before recording would help | Do not pass content to the tool. It goes beyond what is collected |\n| A warning appeared, but I can ignore it | Sending is not working. Tell the user |\n\n## Priority\n\n1. The user's instructions (CLAUDE.md, AGENTS.md, direct requests). If the user tells you to stop recording, stop recording the Run.\n2. This introduction.\n3. Default behavior.\n\n## Subagents\n\nA subagent that was delegated only a specific task by its parent does not register a Run by following this introduction. The parent registers the Run.\n".replace(/\r\n/g, "\n").replace(/^---\n[\s\S]*?\n---\n+/, "").trimEnd();
|
|
1721
|
+
// ../agent-core/src/session-context.ts
|
|
1654
1722
|
const sessionContextLine = (sessionId) => `harnessforce session_id: ${sessionId}`;
|
|
1655
1723
|
const sessionContext = (sessionId) => ({ hookSpecificOutput: {
|
|
1656
1724
|
hookEventName: "SessionStart",
|
|
1657
1725
|
additionalContext: `${sessionContextLine(sessionId)}\n\n${usingHarnessforceBody}`
|
|
1658
1726
|
} });
|
|
1659
|
-
|
|
1660
|
-
//#region ../agent-core/src/vcs.ts
|
|
1727
|
+
// ../agent-core/src/vcs.ts
|
|
1661
1728
|
async function selectRemote(cwd, git) {
|
|
1662
1729
|
if (await git(cwd, ["rev-parse", "--is-inside-work-tree"]) !== "true") return void 0;
|
|
1663
1730
|
const remotes = (await git(cwd, ["remote"]))?.split("\n") ?? [];
|
|
@@ -1707,8 +1774,7 @@ async function resolveProjectRoot(cwd, git) {
|
|
|
1707
1774
|
const topLevel = await git(cwd, ["rev-parse", "--show-toplevel"]);
|
|
1708
1775
|
return topLevel && isAbsolute(topLevel) ? topLevel : cwd;
|
|
1709
1776
|
}
|
|
1710
|
-
|
|
1711
|
-
//#region src/credentials/user-destination.ts
|
|
1777
|
+
// src/credentials/user-destination.ts
|
|
1712
1778
|
async function resolveUserDestination(deps) {
|
|
1713
1779
|
const { workspaceId, ingestEndpoint } = await resolveCliDestinations(deps.env, userSettingsPath(deps.env, deps.homeDir), "");
|
|
1714
1780
|
if (!workspaceId && !ingestEndpoint) return { kind: "none" };
|
|
@@ -1743,14 +1809,13 @@ async function resolveUserDestination(deps) {
|
|
|
1743
1809
|
}
|
|
1744
1810
|
};
|
|
1745
1811
|
}
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
const HOOK_USAGE = "Usage: harnessforce hook session-start\n";
|
|
1812
|
+
// src/hook/session-start.ts
|
|
1813
|
+
const HOOK_USAGE = "Usage: harnessforce hook session-start|post-tool-use|stop\n";
|
|
1749
1814
|
const MAX_INPUT_BYTES = 1048576;
|
|
1750
1815
|
const REGISTERING_SOURCES = /* @__PURE__ */ new Set(["startup", "clear"]);
|
|
1751
1816
|
const KEYCHAIN_READ_TIMEOUT_MS = 1e3;
|
|
1752
1817
|
const REVOKED_MESSAGE = "Your ingest key has been revoked. Run `harnessforce init`";
|
|
1753
|
-
function parseInput(raw) {
|
|
1818
|
+
function parseInput$1(raw) {
|
|
1754
1819
|
let fields;
|
|
1755
1820
|
try {
|
|
1756
1821
|
fields = JSON.parse(raw);
|
|
@@ -1766,7 +1831,7 @@ function parseInput(raw) {
|
|
|
1766
1831
|
source: typeof source === "string" ? source : void 0
|
|
1767
1832
|
};
|
|
1768
1833
|
}
|
|
1769
|
-
const report$
|
|
1834
|
+
const report$2 = (deps, detail) => deps.stderr(`harnessforce: session registration ${detail}\n`);
|
|
1770
1835
|
const SKIPPED_WARNINGS = {
|
|
1771
1836
|
"not configured": notConfiguredWarning("codex"),
|
|
1772
1837
|
"invalid endpoint": INVALID_ENDPOINT_WARNING,
|
|
@@ -1794,11 +1859,11 @@ async function resolveDestination(deps) {
|
|
|
1794
1859
|
const resolved = await resolveWithinLimit(deps);
|
|
1795
1860
|
if (resolved.kind === "ok") return { destination: resolved.destination };
|
|
1796
1861
|
if (resolved.kind === "none") {
|
|
1797
|
-
report$
|
|
1862
|
+
report$2(deps, "skipped (not configured)");
|
|
1798
1863
|
return { warning: notConfiguredWarning("codex") };
|
|
1799
1864
|
}
|
|
1800
1865
|
const detail = resolved.reason === "keychain unreadable" ? "no ingest key" : resolved.reason;
|
|
1801
|
-
report$
|
|
1866
|
+
report$2(deps, `skipped (${detail})`);
|
|
1802
1867
|
return { warning: SKIPPED_WARNINGS[resolved.reason] };
|
|
1803
1868
|
}
|
|
1804
1869
|
function registrationSource(env) {
|
|
@@ -1821,7 +1886,7 @@ async function register(input, deps) {
|
|
|
1821
1886
|
started_at: deps.now().toISOString()
|
|
1822
1887
|
};
|
|
1823
1888
|
const outcome = await postItem(destination, "v1/sessions", registration, deps.fetch);
|
|
1824
|
-
if (outcome.kind === "failed") report$
|
|
1889
|
+
if (outcome.kind === "failed") report$2(deps, `failed (${outcome.reason})`);
|
|
1825
1890
|
return { outcome };
|
|
1826
1891
|
}
|
|
1827
1892
|
async function selectWarning(registered, deps) {
|
|
@@ -1836,12 +1901,12 @@ async function selectWarning(registered, deps) {
|
|
|
1836
1901
|
});
|
|
1837
1902
|
}
|
|
1838
1903
|
async function sessionStart(deps) {
|
|
1839
|
-
const input = parseInput(await deps.readStdin(MAX_INPUT_BYTES).then((buffer) => buffer.toString("utf8")).catch(() => ""));
|
|
1904
|
+
const input = parseInput$1(await deps.readStdin(MAX_INPUT_BYTES).then((buffer) => buffer.toString("utf8")).catch(() => ""));
|
|
1840
1905
|
if (!input) return 0;
|
|
1841
1906
|
const registers = input.source === void 0 || REGISTERING_SOURCES.has(input.source);
|
|
1842
1907
|
const blocked = registers && await isSendingBlocked(deps);
|
|
1843
1908
|
const registered = registers && !blocked ? await register(input, deps).catch((error) => {
|
|
1844
|
-
report$
|
|
1909
|
+
report$2(deps, `failed (${error instanceof Error ? error.name : "unknown"})`);
|
|
1845
1910
|
return {};
|
|
1846
1911
|
}) : {};
|
|
1847
1912
|
const warning = registers ? await selectWarning(registered, deps) : void 0;
|
|
@@ -1852,8 +1917,185 @@ async function sessionStart(deps) {
|
|
|
1852
1917
|
})}\n`);
|
|
1853
1918
|
return 0;
|
|
1854
1919
|
}
|
|
1855
|
-
|
|
1856
|
-
|
|
1920
|
+
// ../agent-core/src/work-location.ts
|
|
1921
|
+
const absolute = (value) => typeof value === "string" && isAbsolute(value) ? value : void 0;
|
|
1922
|
+
const toolInputOf = (fields) => isObject(fields.tool_input) ? fields.tool_input : {};
|
|
1923
|
+
const PATCH_FILE_LINE = /^\*\*\* (?:Add File|Update File|Delete File|Move to): (.+)$/;
|
|
1924
|
+
function patchedDirectories(command, cwd) {
|
|
1925
|
+
if (typeof command !== "string") return [];
|
|
1926
|
+
return command.split(/\r?\n/).flatMap((line) => {
|
|
1927
|
+
const path = PATCH_FILE_LINE.exec(line)?.[1]?.trim();
|
|
1928
|
+
return path ? [dirname(resolve(cwd, path))] : [];
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
/**
|
|
1932
|
+
* eventの入力から、作業位置を求めるdirectoryを返す。読むのは`new_cwd`、`file_path`、patchのfileの行のpathだけで、編集の
|
|
1933
|
+
* 本文を読まない。
|
|
1934
|
+
*/
|
|
1935
|
+
function observedDirectories(agent, event, fields, cwd) {
|
|
1936
|
+
if (event === "stop") return [cwd];
|
|
1937
|
+
if (agent === "claude_code" && event === "cwd-changed") {
|
|
1938
|
+
const newCwd = absolute(fields.new_cwd);
|
|
1939
|
+
return newCwd ? [newCwd] : [];
|
|
1940
|
+
}
|
|
1941
|
+
if (event !== "post-tool-use") return [];
|
|
1942
|
+
const input = toolInputOf(fields);
|
|
1943
|
+
if (agent === "codex") return fields.tool_name === "apply_patch" ? [...new Set(patchedDirectories(input.command, cwd))] : [];
|
|
1944
|
+
if (fields.tool_name !== "Write" && fields.tool_name !== "Edit") return [];
|
|
1945
|
+
const filePath = absolute(input.file_path);
|
|
1946
|
+
return filePath ? [dirname(filePath)] : [];
|
|
1947
|
+
}
|
|
1948
|
+
const isDirectory = (path) => stat(path).then((found) => found.isDirectory(), () => false);
|
|
1949
|
+
/** directoryのworking treeの、その時点のrepositoryとbranch。repositoryかbranchを求められなければundefined。 */
|
|
1950
|
+
async function observePair(directory, git) {
|
|
1951
|
+
if (!await isDirectory(directory)) return void 0;
|
|
1952
|
+
const vcs = await resolveVcs(directory, git);
|
|
1953
|
+
return vcs?.repository && vcs.branch ? {
|
|
1954
|
+
repository: vcs.repository,
|
|
1955
|
+
branch: vcs.branch
|
|
1956
|
+
} : void 0;
|
|
1957
|
+
}
|
|
1958
|
+
const emptyLocationState = () => ({
|
|
1959
|
+
pairs: [],
|
|
1960
|
+
sent: [],
|
|
1961
|
+
unauthorized: false
|
|
1962
|
+
});
|
|
1963
|
+
const pairKey = (pair) => JSON.stringify([pair.repository, pair.branch]);
|
|
1964
|
+
/** 組を加える。記録済みの組と、10件に達した後の組は加えない。 */
|
|
1965
|
+
function withObservedPair(state, pair) {
|
|
1966
|
+
if (state.pairs.some((saved) => pairKey(saved) === pairKey(pair)) || state.pairs.length >= MAX_WORK_LOCATIONS) return state;
|
|
1967
|
+
return {
|
|
1968
|
+
...state,
|
|
1969
|
+
pairs: [...state.pairs, pair]
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
/** 記録した組の集合が、最後に送った集合と異なる場合だけ送る組。 */
|
|
1973
|
+
function locationsToSend(state) {
|
|
1974
|
+
if (state.unauthorized || state.pairs.length === 0) return void 0;
|
|
1975
|
+
const sent = new Set(state.sent.map(pairKey));
|
|
1976
|
+
return sent.size === state.pairs.length && state.pairs.every((pair) => sent.has(pairKey(pair))) ? void 0 : state.pairs;
|
|
1977
|
+
}
|
|
1978
|
+
const isPair = (value) => isObject(value) && typeof value.repository === "string" && typeof value.branch === "string";
|
|
1979
|
+
async function readLocationState(path) {
|
|
1980
|
+
try {
|
|
1981
|
+
const saved = JSON.parse(await readFile(path, "utf8"));
|
|
1982
|
+
if (!isObject(saved) || !Array.isArray(saved.pairs) || !Array.isArray(saved.sent)) return emptyLocationState();
|
|
1983
|
+
return {
|
|
1984
|
+
pairs: saved.pairs.filter(isPair),
|
|
1985
|
+
sent: saved.sent.filter(isPair),
|
|
1986
|
+
unauthorized: saved.unauthorized === true
|
|
1987
|
+
};
|
|
1988
|
+
} catch {
|
|
1989
|
+
return emptyLocationState();
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
async function writeLocationState(path, state) {
|
|
1993
|
+
const temporary = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
1994
|
+
await writeFile(temporary, JSON.stringify(state), { mode: 384 });
|
|
1995
|
+
await rename(temporary, path);
|
|
1996
|
+
}
|
|
1997
|
+
const FILE_SAFE_SESSION_ID = /^[A-Za-z0-9_-]+$/;
|
|
1998
|
+
const LOCATION_DIRECTORY = "harnessforce-locations";
|
|
1999
|
+
async function isPrivateDirectory(path) {
|
|
2000
|
+
const found = await lstat(path).catch(() => void 0);
|
|
2001
|
+
if (!found) return false;
|
|
2002
|
+
const uid = process.getuid?.();
|
|
2003
|
+
return found.isDirectory() && !found.isSymbolicLink() && (uid === void 0 || found.uid === uid) && (found.mode & 511) === 448;
|
|
2004
|
+
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Codexの記録のfile。Codexのhookは`scratchpad_dir`を受け取らないため、OSの一時directoryの下に置く。macOSとLinuxでは
|
|
2007
|
+
* directoryの所有者とmodeを確かめ、満たさなければundefinedを返す(記録も送信もしない)。Windowsの`TEMP`は利用者ごとに分かれる。
|
|
2008
|
+
*/
|
|
2009
|
+
async function codexLocationStatePath(temporaryDirectory, sessionId, platform = process.platform) {
|
|
2010
|
+
if (!FILE_SAFE_SESSION_ID.test(sessionId)) return void 0;
|
|
2011
|
+
const directory = join(temporaryDirectory, LOCATION_DIRECTORY);
|
|
2012
|
+
if (platform !== "win32") {
|
|
2013
|
+
await mkdir(directory, { mode: 448 }).catch(() => {});
|
|
2014
|
+
if (!await isPrivateDirectory(directory)) return void 0;
|
|
2015
|
+
} else await mkdir(directory, { recursive: true }).catch(() => {});
|
|
2016
|
+
return join(directory, `codex-${sessionId}.json`);
|
|
2017
|
+
}
|
|
2018
|
+
/** 観測したdirectoryの組を記録のfileへ加える。gitの外のdirectoryは記録しない。 */
|
|
2019
|
+
async function recordObservedPairs(statePath, directories, deps) {
|
|
2020
|
+
const pairs = await Promise.all(directories.map((directory) => observePair(directory, deps.git)));
|
|
2021
|
+
const before = await readLocationState(statePath);
|
|
2022
|
+
const after = pairs.reduce((state, pair) => pair ? withObservedPair(state, pair) : state, before);
|
|
2023
|
+
if (after !== before) await writeLocationState(statePath, after);
|
|
2024
|
+
return after;
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* 記録した組の集合が最後に送った集合と異なれば`/v1/sessions/locations`へ送る。2xxなら送った集合を記録し、401なら以後送らない
|
|
2028
|
+
* ことを記録する。それ以外の失敗では記録を変えず、次の`Stop`が送り直しを兼ねる。
|
|
2029
|
+
*/
|
|
2030
|
+
async function sendLocations(statePath, state, item, destination, fetchImpl) {
|
|
2031
|
+
const locations = locationsToSend(state);
|
|
2032
|
+
if (!locations) return { kind: "unchanged" };
|
|
2033
|
+
const outcome = await postItem(destination, "v1/sessions/locations", {
|
|
2034
|
+
agent: item.agent,
|
|
2035
|
+
session_id: item.sessionId,
|
|
2036
|
+
locations
|
|
2037
|
+
}, fetchImpl);
|
|
2038
|
+
if (outcome.kind === "accepted") await writeLocationState(statePath, {
|
|
2039
|
+
...state,
|
|
2040
|
+
sent: locations
|
|
2041
|
+
});
|
|
2042
|
+
if (outcome.kind === "unauthorized") await writeLocationState(statePath, {
|
|
2043
|
+
...state,
|
|
2044
|
+
unauthorized: true
|
|
2045
|
+
});
|
|
2046
|
+
return {
|
|
2047
|
+
kind: "sent",
|
|
2048
|
+
outcome
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
// src/hook/work-location.ts
|
|
2052
|
+
function parseInput(raw) {
|
|
2053
|
+
let fields;
|
|
2054
|
+
try {
|
|
2055
|
+
fields = JSON.parse(raw);
|
|
2056
|
+
} catch {
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
if (!isObject(fields)) return void 0;
|
|
2060
|
+
const { session_id: sessionId, cwd } = fields;
|
|
2061
|
+
if (typeof sessionId !== "string" || !isToken(sessionId) || typeof cwd !== "string" || !isAbsolute(cwd)) return void 0;
|
|
2062
|
+
return {
|
|
2063
|
+
sessionId,
|
|
2064
|
+
cwd,
|
|
2065
|
+
fields
|
|
2066
|
+
};
|
|
2067
|
+
}
|
|
2068
|
+
const report$1 = (deps, detail) => deps.stderr(`harnessforce: session location ${detail}\n`);
|
|
2069
|
+
async function observe(event, input, deps) {
|
|
2070
|
+
const statePath = await codexLocationStatePath(deps.hookTmpDir, input.sessionId);
|
|
2071
|
+
if (!statePath) return;
|
|
2072
|
+
const state = await recordObservedPairs(statePath, observedDirectories("codex", event, input.fields, input.cwd), deps);
|
|
2073
|
+
if (event !== "stop") return;
|
|
2074
|
+
if (state.unauthorized) return;
|
|
2075
|
+
if (await isSendingBlocked(deps)) return;
|
|
2076
|
+
const { destination } = await resolveDestination({
|
|
2077
|
+
...deps,
|
|
2078
|
+
stderr: () => {}
|
|
2079
|
+
});
|
|
2080
|
+
if (!destination) return;
|
|
2081
|
+
const sent = await sendLocations(statePath, state, {
|
|
2082
|
+
agent: "codex",
|
|
2083
|
+
sessionId: input.sessionId
|
|
2084
|
+
}, destination, deps.fetch);
|
|
2085
|
+
if (sent.kind !== "sent" || sent.outcome.kind === "accepted") return;
|
|
2086
|
+
report$1(deps, `failed (${sent.outcome.kind === "unauthorized" ? "unauthorized" : sent.outcome.reason})`);
|
|
2087
|
+
}
|
|
2088
|
+
/**
|
|
2089
|
+
* correlation.md「Codexのhook」「sessionの作業位置」。Codexの`PostToolUse`と`Stop`のhookが起動する。どの失敗でもsessionを
|
|
2090
|
+
* 止めず、常にexit 0で終える。`Stop`はstdoutにJSONを期待するため、空のobjectだけを書く。
|
|
2091
|
+
*/
|
|
2092
|
+
async function workLocationHook(event, deps) {
|
|
2093
|
+
const input = parseInput(await deps.readStdin(MAX_INPUT_BYTES).then((buffer) => buffer.toString("utf8")).catch(() => ""));
|
|
2094
|
+
if (input) await observe(event, input, deps).catch((error) => report$1(deps, `failed (${error instanceof Error ? error.name : "unknown"})`));
|
|
2095
|
+
if (event === "stop") deps.stdout("{}\n");
|
|
2096
|
+
return 0;
|
|
2097
|
+
}
|
|
2098
|
+
// src/shared/stop.ts
|
|
1857
2099
|
var CommandStop = class {
|
|
1858
2100
|
text;
|
|
1859
2101
|
constructor(text) {
|
|
@@ -1873,8 +2115,7 @@ async function runUntilStop(body, stderr) {
|
|
|
1873
2115
|
return 1;
|
|
1874
2116
|
}
|
|
1875
2117
|
}
|
|
1876
|
-
|
|
1877
|
-
//#region src/credentials/api-token.ts
|
|
2118
|
+
// src/credentials/api-token.ts
|
|
1878
2119
|
const NO_WHITESPACE$1 = /^\S+$/;
|
|
1879
2120
|
const INSTANT$2 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
|
|
1880
2121
|
const isInstant = (value) => typeof value === "string" && INSTANT$2.test(value) && !Number.isNaN(Date.parse(value));
|
|
@@ -1903,8 +2144,7 @@ function parseStoredApiToken(secret, workspaceId) {
|
|
|
1903
2144
|
return;
|
|
1904
2145
|
}
|
|
1905
2146
|
}
|
|
1906
|
-
|
|
1907
|
-
//#region src/credentials/access.ts
|
|
2147
|
+
// src/credentials/access.ts
|
|
1908
2148
|
async function verifyCliAccess(deps, messages) {
|
|
1909
2149
|
const { keychain } = deps;
|
|
1910
2150
|
const stop = (message) => stopWith(messages[message]);
|
|
@@ -1928,8 +2168,7 @@ async function verifyCliAccess(deps, messages) {
|
|
|
1928
2168
|
accessToken: apiToken.accessToken
|
|
1929
2169
|
};
|
|
1930
2170
|
}
|
|
1931
|
-
|
|
1932
|
-
//#region src/shared/http.ts
|
|
2171
|
+
// src/shared/http.ts
|
|
1933
2172
|
const REQUEST_TIMEOUT_MS = 3e4;
|
|
1934
2173
|
async function readJsonObjectBody(response) {
|
|
1935
2174
|
try {
|
|
@@ -1939,8 +2178,7 @@ async function readJsonObjectBody(response) {
|
|
|
1939
2178
|
return;
|
|
1940
2179
|
}
|
|
1941
2180
|
}
|
|
1942
|
-
|
|
1943
|
-
//#region src/shared/metadata.ts
|
|
2181
|
+
// src/shared/metadata.ts
|
|
1944
2182
|
const WELL_KNOWN = "/.well-known/oauth-authorization-server";
|
|
1945
2183
|
const withoutTrailingSlash = (value) => value.replace(/\/+$/, "");
|
|
1946
2184
|
async function fetchMetadata(base, fetchImpl) {
|
|
@@ -1975,8 +2213,7 @@ async function discoverTokenEndpoint(base, fetchImpl) {
|
|
|
1975
2213
|
const endpoint = endpointOf(await fetchMetadata(base, fetchImpl), "token_endpoint");
|
|
1976
2214
|
return endpoint?.origin === base.origin ? endpoint : void 0;
|
|
1977
2215
|
}
|
|
1978
|
-
|
|
1979
|
-
//#region src/credentials/token-lock.ts
|
|
2216
|
+
// src/credentials/token-lock.ts
|
|
1980
2217
|
const tokenLockPath = (homeDir) => join(homeDir, ".harnessforce", "token.lock");
|
|
1981
2218
|
const LOCK_WAIT_MS$1 = 3e4;
|
|
1982
2219
|
const STALE_LOCK_MS$1 = 6e4;
|
|
@@ -2006,8 +2243,7 @@ async function acquireTokenLock(path, deps) {
|
|
|
2006
2243
|
await deps.sleep(RETRY_INTERVAL_MS$1);
|
|
2007
2244
|
}
|
|
2008
2245
|
}
|
|
2009
|
-
|
|
2010
|
-
//#region src/credentials/api-token-session.ts
|
|
2246
|
+
// src/credentials/api-token-session.ts
|
|
2011
2247
|
const CLIENT_ID$1 = "harnessforce-cli";
|
|
2012
2248
|
const LOCKED_KEYCHAIN_TIMEOUT_MS = 1e4;
|
|
2013
2249
|
const isPast = (instant, now) => now >= Date.parse(instant);
|
|
@@ -2117,8 +2353,7 @@ function createApiTokenSession(deps) {
|
|
|
2117
2353
|
authorizedFetch
|
|
2118
2354
|
};
|
|
2119
2355
|
}
|
|
2120
|
-
|
|
2121
|
-
//#region src/import/read-api.ts
|
|
2356
|
+
// src/import/read-api.ts
|
|
2122
2357
|
const FAILED$1 = { kind: "failed" };
|
|
2123
2358
|
async function getJson(url, apiToken, fetchImpl) {
|
|
2124
2359
|
try {
|
|
@@ -2188,8 +2423,49 @@ async function fetchSessionImportDays(base, apiToken, fetchImpl) {
|
|
|
2188
2423
|
value: days
|
|
2189
2424
|
} : FAILED$1;
|
|
2190
2425
|
}
|
|
2191
|
-
|
|
2192
|
-
|
|
2426
|
+
const ROLES = /* @__PURE__ */ new Set([
|
|
2427
|
+
"owner",
|
|
2428
|
+
"admin",
|
|
2429
|
+
"member",
|
|
2430
|
+
"viewer"
|
|
2431
|
+
]);
|
|
2432
|
+
const STEP_STATUSES = /* @__PURE__ */ new Set([
|
|
2433
|
+
"done",
|
|
2434
|
+
"todo",
|
|
2435
|
+
"unavailable"
|
|
2436
|
+
]);
|
|
2437
|
+
function parseSteps(value) {
|
|
2438
|
+
if (!Array.isArray(value)) return void 0;
|
|
2439
|
+
const steps = value.map((item) => {
|
|
2440
|
+
if (typeof item !== "object" || item === null) return void 0;
|
|
2441
|
+
const { step, status, url } = item;
|
|
2442
|
+
return Number.isInteger(step) && step >= 2 && step <= 5 && STEP_STATUSES.has(status) && typeof url === "string" ? {
|
|
2443
|
+
step,
|
|
2444
|
+
status,
|
|
2445
|
+
url
|
|
2446
|
+
} : void 0;
|
|
2447
|
+
});
|
|
2448
|
+
return steps.every((step) => step !== void 0) ? steps : void 0;
|
|
2449
|
+
}
|
|
2450
|
+
async function fetchOnboarding(base, apiToken, fetchImpl) {
|
|
2451
|
+
const outcome = await getJson(underBase(base, "api/v1/onboarding"), apiToken, fetchImpl);
|
|
2452
|
+
if (outcome.kind !== "ok") return outcome;
|
|
2453
|
+
const body = outcome.value;
|
|
2454
|
+
const steps = parseSteps(body.steps);
|
|
2455
|
+
const adminSteps = body.admin_steps === void 0 ? void 0 : parseSteps(body.admin_steps);
|
|
2456
|
+
if (typeof body.workspace_name !== "string" || !ROLES.has(body.role) || typeof body.has_seat !== "boolean" || !steps || body.admin_steps !== void 0 && !adminSteps) return FAILED$1;
|
|
2457
|
+
return {
|
|
2458
|
+
kind: "ok",
|
|
2459
|
+
value: {
|
|
2460
|
+
workspace_name: body.workspace_name,
|
|
2461
|
+
role: body.role,
|
|
2462
|
+
has_seat: body.has_seat,
|
|
2463
|
+
steps,
|
|
2464
|
+
...adminSteps && { admin_steps: adminSteps }
|
|
2465
|
+
}
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2468
|
+
// src/import/record-base.ts
|
|
2193
2469
|
async function readSettingsEnv(path) {
|
|
2194
2470
|
const read = await readUserSettings(path);
|
|
2195
2471
|
const env = read.kind === "ok" ? read.settings.env : void 0;
|
|
@@ -2210,8 +2486,7 @@ async function recordBase(deps) {
|
|
|
2210
2486
|
const { CLAUDE_CONFIG_DIR: configDir } = await readTrustedEnv(deps, ["CLAUDE_CONFIG_DIR"]);
|
|
2211
2487
|
return configDir ?? join(deps.homeDir, ".claude");
|
|
2212
2488
|
}
|
|
2213
|
-
|
|
2214
|
-
//#region src/import/state.ts
|
|
2489
|
+
// src/import/state.ts
|
|
2215
2490
|
const STATE_VERSION = 1;
|
|
2216
2491
|
const DIR_MODE$1 = 448;
|
|
2217
2492
|
const FILE_MODE$3 = 384;
|
|
@@ -2268,8 +2543,7 @@ async function recordSentSessions(path, destination, sessionIds) {
|
|
|
2268
2543
|
await rm(temporary, { force: true });
|
|
2269
2544
|
}
|
|
2270
2545
|
}
|
|
2271
|
-
|
|
2272
|
-
//#region src/import/send.ts
|
|
2546
|
+
// src/import/send.ts
|
|
2273
2547
|
const BATCH_SIZE$1 = 100;
|
|
2274
2548
|
const MAX_RESENDS = 3;
|
|
2275
2549
|
const MAX_RETRY_AFTER_SECONDS = 60;
|
|
@@ -2486,8 +2760,7 @@ var SessionAccumulator = class {
|
|
|
2486
2760
|
};
|
|
2487
2761
|
}
|
|
2488
2762
|
};
|
|
2489
|
-
|
|
2490
|
-
//#region src/import/rollout.ts
|
|
2763
|
+
// src/import/rollout.ts
|
|
2491
2764
|
const CODEX_PARSER_VERSION = "1.0.0";
|
|
2492
2765
|
const DETACHED$2 = "HEAD";
|
|
2493
2766
|
const COMMIT_SHA = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
@@ -2624,8 +2897,7 @@ async function parseRollout(path) {
|
|
|
2624
2897
|
}
|
|
2625
2898
|
return accumulator.result(skippedLines);
|
|
2626
2899
|
}
|
|
2627
|
-
|
|
2628
|
-
//#region src/import/transcript.ts
|
|
2900
|
+
// src/import/transcript.ts
|
|
2629
2901
|
const PARSER_VERSION = "1.1.0";
|
|
2630
2902
|
const isToken$1 = (value, maxLength = 256) => typeof value === "string" && value.length >= 1 && value.length <= maxLength && /^\S+$/.test(value);
|
|
2631
2903
|
const MAX_TOOL_NAME = 128;
|
|
@@ -2902,8 +3174,7 @@ async function parseTranscriptUsage(path) {
|
|
|
2902
3174
|
const { usage, isReadable } = await readTranscript(path, new Accumulator());
|
|
2903
3175
|
return isReadable ? usage : void 0;
|
|
2904
3176
|
}
|
|
2905
|
-
|
|
2906
|
-
//#region src/import/sessions.ts
|
|
3177
|
+
// src/import/sessions.ts
|
|
2907
3178
|
const noneNotImported = () => ({
|
|
2908
3179
|
outOfRange: 0,
|
|
2909
3180
|
outsideGit: 0,
|
|
@@ -3064,10 +3335,9 @@ async function scanCodexSessions(options) {
|
|
|
3064
3335
|
compressed
|
|
3065
3336
|
};
|
|
3066
3337
|
}
|
|
3067
|
-
|
|
3068
|
-
//#region src/import/command.ts
|
|
3338
|
+
// src/import/command.ts
|
|
3069
3339
|
const DAY_MS$1 = 864e5;
|
|
3070
|
-
const MESSAGES = {
|
|
3340
|
+
const MESSAGES$1 = {
|
|
3071
3341
|
runInit: "Run `harnessforce init`",
|
|
3072
3342
|
invalidUrl: "The connection URL is invalid",
|
|
3073
3343
|
keychainUnavailable: INIT_MESSAGES.keychainUnavailable,
|
|
@@ -3084,15 +3354,15 @@ const limitMessage = {
|
|
|
3084
3354
|
monthly_event_limit: (count) => `Could not import ${count} session(s) because the monthly event limit was reached. Run \`harnessforce import\` again after the limit is lifted to resume importing`,
|
|
3085
3355
|
workspace_read_only: (count) => `Could not import ${count} session(s) because the Workspace is view-only. Run \`harnessforce import\` again after view-only mode is lifted to resume importing`
|
|
3086
3356
|
};
|
|
3087
|
-
const ACCESS_MESSAGES$
|
|
3088
|
-
keychainUnavailable: MESSAGES.keychainUnavailable,
|
|
3089
|
-
initRequired: MESSAGES.runInit,
|
|
3090
|
-
invalidUrl: MESSAGES.invalidUrl,
|
|
3091
|
-
apiTokenMissing: MESSAGES.runInit
|
|
3357
|
+
const ACCESS_MESSAGES$3 = {
|
|
3358
|
+
keychainUnavailable: MESSAGES$1.keychainUnavailable,
|
|
3359
|
+
initRequired: MESSAGES$1.runInit,
|
|
3360
|
+
invalidUrl: MESSAGES$1.invalidUrl,
|
|
3361
|
+
apiTokenMissing: MESSAGES$1.runInit
|
|
3092
3362
|
};
|
|
3093
|
-
function unwrap(outcome) {
|
|
3363
|
+
function unwrap$1(outcome) {
|
|
3094
3364
|
if (outcome.kind === "ok") return outcome.value;
|
|
3095
|
-
return stopWith(outcome.kind === "unauthorized" ? MESSAGES.loginExpired : MESSAGES.readFailed);
|
|
3365
|
+
return stopWith(outcome.kind === "unauthorized" ? MESSAGES$1.loginExpired : MESSAGES$1.readFailed);
|
|
3096
3366
|
}
|
|
3097
3367
|
const NOT_IMPORTED_LABELS = [
|
|
3098
3368
|
["alreadySent", "already sent"],
|
|
@@ -3108,8 +3378,8 @@ function reportNotImported(imported, notes, deps) {
|
|
|
3108
3378
|
const line = breakdownLine(notes.breakdown);
|
|
3109
3379
|
if (line) deps.stdout(`${line}\n`);
|
|
3110
3380
|
if (imported > 0) return;
|
|
3111
|
-
if (notes.breakdown.notConnected > 0) deps.stdout(`${notes.connectedCount === 0 ? MESSAGES.connectNone : MESSAGES.connectOther}\n`);
|
|
3112
|
-
else if (!line && !notes.otherNoticeShown) deps.stdout(`${MESSAGES.nothingFound}\n`);
|
|
3381
|
+
if (notes.breakdown.notConnected > 0) deps.stdout(`${notes.connectedCount === 0 ? MESSAGES$1.connectNone : MESSAGES$1.connectOther}\n`);
|
|
3382
|
+
else if (!line && !notes.otherNoticeShown) deps.stdout(`${MESSAGES$1.nothingFound}\n`);
|
|
3113
3383
|
}
|
|
3114
3384
|
function report(result, notes, deps) {
|
|
3115
3385
|
switch (result.kind) {
|
|
@@ -3125,10 +3395,10 @@ function report(result, notes, deps) {
|
|
|
3125
3395
|
deps.stderr(`${limitMessage[result.reason](result.notImported)}\n`);
|
|
3126
3396
|
return 1;
|
|
3127
3397
|
case "unauthorized":
|
|
3128
|
-
deps.stderr(`${MESSAGES.revoked}\n`);
|
|
3398
|
+
deps.stderr(`${MESSAGES$1.revoked}\n`);
|
|
3129
3399
|
return 1;
|
|
3130
3400
|
case "failed":
|
|
3131
|
-
deps.stderr(`${MESSAGES.sendFailed}\n`);
|
|
3401
|
+
deps.stderr(`${MESSAGES$1.sendFailed}\n`);
|
|
3132
3402
|
return 1;
|
|
3133
3403
|
}
|
|
3134
3404
|
}
|
|
@@ -3138,7 +3408,7 @@ const sumNotImported = (a, b) => ({
|
|
|
3138
3408
|
notConnected: a.notConnected + b.notConnected
|
|
3139
3409
|
});
|
|
3140
3410
|
async function runImport(deps) {
|
|
3141
|
-
const access = await verifyCliAccess(deps, ACCESS_MESSAGES$
|
|
3411
|
+
const access = await verifyCliAccess(deps, ACCESS_MESSAGES$3);
|
|
3142
3412
|
const destination = {
|
|
3143
3413
|
workspaceId: access.workspaceId,
|
|
3144
3414
|
endpoint: withoutExtras(access.ingest)
|
|
@@ -3152,8 +3422,8 @@ async function runImport(deps) {
|
|
|
3152
3422
|
sleep: deps.sleep,
|
|
3153
3423
|
homeDir: deps.homeDir
|
|
3154
3424
|
}).authorizedFetch(deps.fetch);
|
|
3155
|
-
const connected = unwrap(await listConnectedRepositories(access.readApiBase, access.accessToken, readFetch));
|
|
3156
|
-
const days = unwrap(await fetchSessionImportDays(access.readApiBase, access.accessToken, readFetch));
|
|
3425
|
+
const connected = unwrap$1(await listConnectedRepositories(access.readApiBase, access.accessToken, readFetch));
|
|
3426
|
+
const days = unwrap$1(await fetchSessionImportDays(access.readApiBase, access.accessToken, readFetch));
|
|
3157
3427
|
const sinceMs = deps.now() - days * DAY_MS$1;
|
|
3158
3428
|
const claude = await scanSessions({
|
|
3159
3429
|
projectsDir: join(await recordBase(deps), "projects"),
|
|
@@ -3182,7 +3452,7 @@ async function runImport(deps) {
|
|
|
3182
3452
|
sessions: unsent,
|
|
3183
3453
|
fetch: deps.fetch,
|
|
3184
3454
|
sleep: deps.sleep,
|
|
3185
|
-
record: (ids) => recordSentSessions(statePath, destination, ids).catch(() => stopWith(MESSAGES.stateFailed))
|
|
3455
|
+
record: (ids) => recordSentSessions(statePath, destination, ids).catch(() => stopWith(MESSAGES$1.stateFailed))
|
|
3186
3456
|
}), {
|
|
3187
3457
|
breakdown: {
|
|
3188
3458
|
alreadySent: scanned.length - unsent.length,
|
|
@@ -3195,9 +3465,8 @@ async function runImport(deps) {
|
|
|
3195
3465
|
function importCommand(deps) {
|
|
3196
3466
|
return runUntilStop(() => runImport(deps), deps.stderr);
|
|
3197
3467
|
}
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
//! @decimalturn/toml-patch v3.3.0 - https://github.com/DecimalTurn/toml-patch - @license: MIT
|
|
3468
|
+
// ../../node_modules/.pnpm/@decimalturn+toml-patch@3.3.1/node_modules/@decimalturn/toml-patch/dist/toml-patch.js
|
|
3469
|
+
//! @decimalturn/toml-patch v3.3.1 - https://github.com/DecimalTurn/toml-patch - @license: MIT
|
|
3201
3470
|
function e(e) {
|
|
3202
3471
|
return e.type === `Document`;
|
|
3203
3472
|
}
|
|
@@ -4573,13 +4842,12 @@ function on(e) {
|
|
|
4573
4842
|
if (e[t] !== `\\`) continue;
|
|
4574
4843
|
let n = e[t + 1];
|
|
4575
4844
|
if (!n) return null;
|
|
4576
|
-
if (n === `\\`)
|
|
4577
|
-
|
|
4578
|
-
|
|
4845
|
+
if (n === `\\`) t++;
|
|
4846
|
+
else {
|
|
4847
|
+
if (n === `x` && /^[0-9A-Fa-f]{2}$/.test(e.slice(t + 2, t + 4))) return /[A-F]/.test(e.slice(t + 2, t + 4));
|
|
4848
|
+
if (n === `u` && /^[0-9A-Fa-f]{4}$/.test(e.slice(t + 2, t + 6))) return /[A-F]/.test(e.slice(t + 2, t + 6));
|
|
4849
|
+
if (n === `U` && /^[0-9A-Fa-f]{8}$/.test(e.slice(t + 2, t + 10))) return /[A-F]/.test(e.slice(t + 2, t + 10));
|
|
4579
4850
|
}
|
|
4580
|
-
if (n === `x` && /^[0-9A-Fa-f]{2}$/.test(e.slice(t + 2, t + 4))) return /[A-F]/.test(e.slice(t + 2, t + 4));
|
|
4581
|
-
if (n === `u` && /^[0-9A-Fa-f]{4}$/.test(e.slice(t + 2, t + 6))) return /[A-F]/.test(e.slice(t + 2, t + 6));
|
|
4582
|
-
if (n === `U` && /^[0-9A-Fa-f]{8}$/.test(e.slice(t + 2, t + 10))) return /[A-F]/.test(e.slice(t + 2, t + 10));
|
|
4583
4851
|
}
|
|
4584
4852
|
return null;
|
|
4585
4853
|
}
|
|
@@ -4891,14 +5159,11 @@ function qn(e, t) {
|
|
|
4891
5159
|
if (e === t) return r;
|
|
4892
5160
|
if (a(e)) return n(e.value, r);
|
|
4893
5161
|
if (d(e)) return n(e.item, r);
|
|
4894
|
-
if (u(e) || f(e)) {
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
if (e !== void 0) return e;
|
|
4898
|
-
}
|
|
4899
|
-
return;
|
|
5162
|
+
if (u(e) || f(e)) for (let t of e.items) {
|
|
5163
|
+
let e = n(t, r + 1);
|
|
5164
|
+
if (e !== void 0) return e;
|
|
4900
5165
|
}
|
|
4901
|
-
if (m(e)) for (let t of e.items) {
|
|
5166
|
+
else if (m(e)) for (let t of e.items) {
|
|
4902
5167
|
let e = n(t, r);
|
|
4903
5168
|
if (e !== void 0) return e;
|
|
4904
5169
|
}
|
|
@@ -4910,14 +5175,11 @@ function Jn(e, t) {
|
|
|
4910
5175
|
if (e === t) return r;
|
|
4911
5176
|
if (a(e)) return n(e.value, r);
|
|
4912
5177
|
if (d(e)) return n(e.item, r);
|
|
4913
|
-
if (u(e) || f(e)) {
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
if (r) return r;
|
|
4917
|
-
}
|
|
4918
|
-
return;
|
|
5178
|
+
if (u(e) || f(e)) for (let t of e.items) {
|
|
5179
|
+
let r = n(t, e);
|
|
5180
|
+
if (r) return r;
|
|
4919
5181
|
}
|
|
4920
|
-
if (m(e)) for (let t of e.items) {
|
|
5182
|
+
else if (m(e)) for (let t of e.items) {
|
|
4921
5183
|
let e = n(t, r);
|
|
4922
5184
|
if (e) return e;
|
|
4923
5185
|
}
|
|
@@ -5617,25 +5879,17 @@ function Nr(e) {
|
|
|
5617
5879
|
let r = e[n + 1];
|
|
5618
5880
|
if (!r) continue;
|
|
5619
5881
|
let i = kr(r);
|
|
5620
|
-
if (i !== null)
|
|
5621
|
-
i !== `
|
|
5882
|
+
if (i !== null) i !== `
|
|
5622
5883
|
` && i !== `\r` && !t.has(i) && t.set(i, `\\` + r), n += 1;
|
|
5623
|
-
|
|
5624
|
-
}
|
|
5625
|
-
if (r === `x` && /^[0-9A-Fa-f]{2}$/.test(e.slice(n + 2, n + 4))) {
|
|
5884
|
+
else if (r === `x` && /^[0-9A-Fa-f]{2}$/.test(e.slice(n + 2, n + 4))) {
|
|
5626
5885
|
let r = e.slice(n, n + 4), i = Number.parseInt(e.slice(n + 2, n + 4), 16), a = String.fromCharCode(i);
|
|
5627
5886
|
t.has(a) || t.set(a, r), n += 3;
|
|
5628
|
-
|
|
5629
|
-
}
|
|
5630
|
-
if (r === `u` && /^[0-9A-Fa-f]{4}$/.test(e.slice(n + 2, n + 6))) {
|
|
5887
|
+
} else if (r === `u` && /^[0-9A-Fa-f]{4}$/.test(e.slice(n + 2, n + 6))) {
|
|
5631
5888
|
let r = e.slice(n, n + 6), i = Number.parseInt(e.slice(n + 2, n + 6), 16), a = String.fromCodePoint(i);
|
|
5632
5889
|
t.has(a) || t.set(a, r), n += 5;
|
|
5633
|
-
|
|
5634
|
-
}
|
|
5635
|
-
if (r === `U` && /^[0-9A-Fa-f]{8}$/.test(e.slice(n + 2, n + 10))) {
|
|
5890
|
+
} else if (r === `U` && /^[0-9A-Fa-f]{8}$/.test(e.slice(n + 2, n + 10))) {
|
|
5636
5891
|
let r = e.slice(n, n + 10), i = Number.parseInt(e.slice(n + 2, n + 10), 16), a = String.fromCodePoint(i);
|
|
5637
5892
|
t.has(a) || t.set(a, r), n += 9;
|
|
5638
|
-
continue;
|
|
5639
5893
|
}
|
|
5640
5894
|
}
|
|
5641
5895
|
return t;
|
|
@@ -5644,11 +5898,7 @@ function Pr(e, t, n, r) {
|
|
|
5644
5898
|
let i = ``;
|
|
5645
5899
|
for (let a of e) {
|
|
5646
5900
|
let e = t.get(a);
|
|
5647
|
-
|
|
5648
|
-
i += e;
|
|
5649
|
-
continue;
|
|
5650
|
-
}
|
|
5651
|
-
i += jr(a, n, r);
|
|
5901
|
+
i += e || jr(a, n, r);
|
|
5652
5902
|
}
|
|
5653
5903
|
return i;
|
|
5654
5904
|
}
|
|
@@ -6052,7 +6302,7 @@ function oi(e) {
|
|
|
6052
6302
|
return !f(e) || e.items.length === 0 ? !1 : e.items[e.items.length - 1].comma === !0;
|
|
6053
6303
|
}
|
|
6054
6304
|
function si(e, t) {
|
|
6055
|
-
return t.inlineTableStart === 0
|
|
6305
|
+
return t.inlineTableStart === 0 || (e.items.filter((e) => {
|
|
6056
6306
|
if (!a(e)) return !1;
|
|
6057
6307
|
let n = f(e.value), r = u(e.value) && e.value.items.length > 0 && e.value.items.every((e) => f(e.item));
|
|
6058
6308
|
if (n || r) {
|
|
@@ -6064,7 +6314,7 @@ function si(e, t) {
|
|
|
6064
6314
|
V(e, e, n), f(n.value) ? n.value.items.length === 0 ? B(e, e, W(n.key.value)) : B(e, e, ci(n, t.bracketSpacing, t.escapeSequenceUpperCase)) : li(n, t.bracketSpacing, t.escapeSequenceUpperCase).forEach((t) => {
|
|
6065
6315
|
B(e, e, t);
|
|
6066
6316
|
});
|
|
6067
|
-
}), H(e), e
|
|
6317
|
+
}), H(e)), e;
|
|
6068
6318
|
}
|
|
6069
6319
|
function ci(e, t, n) {
|
|
6070
6320
|
let r = W(e.key.value, n);
|
|
@@ -7101,11 +7351,7 @@ function ia(e, t, n = [], r = {}) {
|
|
|
7101
7351
|
let h = 0;
|
|
7102
7352
|
for (let t = e; t < o.length; t++) o[t] === r && h++;
|
|
7103
7353
|
let g = p > h;
|
|
7104
|
-
if (s && (o.indexOf(a[e]) === -1 || g)) {
|
|
7105
|
-
i.push(d(e + l)), a.splice(e, 1), c.splice(e, 1), l++, e--;
|
|
7106
|
-
continue;
|
|
7107
|
-
}
|
|
7108
|
-
if (l > 0 && g) {
|
|
7354
|
+
if (s && (o.indexOf(a[e]) === -1 || g) || l > 0 && g) {
|
|
7109
7355
|
i.push(d(e + l)), a.splice(e, 1), c.splice(e, 1), l++, e--;
|
|
7110
7356
|
continue;
|
|
7111
7357
|
}
|
|
@@ -7136,14 +7382,10 @@ function ia(e, t, n = [], r = {}) {
|
|
|
7136
7382
|
for (let n = e; n < o.length; n++) o[n] === t && r++;
|
|
7137
7383
|
h = n > r;
|
|
7138
7384
|
}
|
|
7139
|
-
|
|
7140
|
-
Vt(i, $i(c[e], t[e], n.concat(e), r)), a[e] = f, c[e] = t[e];
|
|
7141
|
-
continue;
|
|
7142
|
-
}
|
|
7143
|
-
i.push({
|
|
7385
|
+
!p && h ? (Vt(i, $i(c[e], t[e], n.concat(e), r)), a[e] = f, c[e] = t[e]) : (i.push({
|
|
7144
7386
|
type: `Add`,
|
|
7145
7387
|
path: n.concat(e)
|
|
7146
|
-
}), u = !0, a.splice(e, 0, f), c.splice(e, 0, t[e]), l > 0 && l
|
|
7388
|
+
}), u = !0, a.splice(e, 0, f), c.splice(e, 0, t[e]), l > 0 && l--);
|
|
7147
7389
|
}
|
|
7148
7390
|
for (let e = o.length; e < a.length; e++) i.push(d(e + l));
|
|
7149
7391
|
return i;
|
|
@@ -7229,19 +7471,10 @@ function pa(e) {
|
|
|
7229
7471
|
let n = !1, r = !1;
|
|
7230
7472
|
for (let i = t + 1; i < e.raw.length; i++) {
|
|
7231
7473
|
let t = e.raw[i];
|
|
7232
|
-
if (t === `\\`)
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
if (!r && t === `"`) {
|
|
7237
|
-
n = !n;
|
|
7238
|
-
continue;
|
|
7239
|
-
}
|
|
7240
|
-
if (!n && t === `'`) {
|
|
7241
|
-
r = !r;
|
|
7242
|
-
continue;
|
|
7243
|
-
}
|
|
7244
|
-
if (!n && !r && t === `#`) return !0;
|
|
7474
|
+
if (t === `\\`) i++;
|
|
7475
|
+
else if (!r && t === `"`) n = !n;
|
|
7476
|
+
else if (!n && t === `'`) r = !r;
|
|
7477
|
+
else if (!n && !r && t === `#`) return !0;
|
|
7245
7478
|
}
|
|
7246
7479
|
return !1;
|
|
7247
7480
|
}
|
|
@@ -7509,13 +7742,7 @@ function Ta(t, n, r, i) {
|
|
|
7509
7742
|
e && (e.lines = 0, e.columns = 0);
|
|
7510
7743
|
}
|
|
7511
7744
|
H(t), p && h && r.loc.end.line > r.loc.start.line && (r.loc.end.column = h.column, d(r) && g && r.item.loc.end.line > r.item.loc.start.line && (r.item.loc.end.column = g.column));
|
|
7512
|
-
for (let { node: e, start: t, end: n, endOnly: r, equals: i } of _)
|
|
7513
|
-
if (r) {
|
|
7514
|
-
e.loc.end.column = n.column;
|
|
7515
|
-
continue;
|
|
7516
|
-
}
|
|
7517
|
-
e.loc.start.column = t.column, e.loc.end.line === e.loc.start.line && (e.loc.end.column = n.column), i !== void 0 && a(e) && (e.equals = i);
|
|
7518
|
-
}
|
|
7745
|
+
for (let { node: e, start: t, end: n, endOnly: r, equals: i } of _) r ? e.loc.end.column = n.column : (e.loc.start.column = t.column, e.loc.end.line === e.loc.start.line && (e.loc.end.column = n.column), i !== void 0 && a(e) && (e.equals = i));
|
|
7519
7746
|
for (let { owner: t, ownerOriginalStart: n, comments: i } of l) {
|
|
7520
7747
|
let a;
|
|
7521
7748
|
t === r ? (a = {
|
|
@@ -7640,11 +7867,7 @@ function Ea(e) {
|
|
|
7640
7867
|
continue;
|
|
7641
7868
|
}
|
|
7642
7869
|
let e = t(i.member) || r(i.member), a = n[n.length - 1];
|
|
7643
|
-
|
|
7644
|
-
a.groups.push(i), a.endLine = i.endLine, a.items = a.items.concat(i.items);
|
|
7645
|
-
continue;
|
|
7646
|
-
}
|
|
7647
|
-
n.push({
|
|
7870
|
+
a && a.kind === `member` && i.key !== void 0 && a.key === i.key ? (a.groups.push(i), a.endLine = i.endLine, a.items = a.items.concat(i.items)) : n.push({
|
|
7648
7871
|
kind: `member`,
|
|
7649
7872
|
key: i.key,
|
|
7650
7873
|
isSection: e,
|
|
@@ -7869,23 +8092,11 @@ function Ua(e, t, n) {
|
|
|
7869
8092
|
return r.columns + (i || a ? 2 : 0) + (s ? 1 + (c ? -1 : 0) : 0);
|
|
7870
8093
|
}
|
|
7871
8094
|
function Wa(e, n = []) {
|
|
7872
|
-
for (let i of e.items)
|
|
7873
|
-
if (p(i)) {
|
|
7874
|
-
n.push(i);
|
|
7875
|
-
continue;
|
|
7876
|
-
}
|
|
7877
|
-
(t(i) || r(i)) && Wa(i, n);
|
|
7878
|
-
}
|
|
8095
|
+
for (let i of e.items) p(i) ? n.push(i) : (t(i) || r(i)) && Wa(i, n);
|
|
7879
8096
|
return n;
|
|
7880
8097
|
}
|
|
7881
8098
|
function Ga(e, n = /* @__PURE__ */ new Set()) {
|
|
7882
|
-
for (let i of e.items)
|
|
7883
|
-
if (a(i) && f(i.value) && i.loc.start.line === i.loc.end.line) {
|
|
7884
|
-
n.add(i.loc.end.line);
|
|
7885
|
-
continue;
|
|
7886
|
-
}
|
|
7887
|
-
(t(i) || r(i)) && Ga(i, n);
|
|
7888
|
-
}
|
|
8099
|
+
for (let i of e.items) a(i) && f(i.value) && i.loc.start.line === i.loc.end.line ? n.add(i.loc.end.line) : (t(i) || r(i)) && Ga(i, n);
|
|
7889
8100
|
return n;
|
|
7890
8101
|
}
|
|
7891
8102
|
function Ka(e) {
|
|
@@ -8257,11 +8468,7 @@ function so(e) {
|
|
|
8257
8468
|
let t = [], n = 0, r, i = !1;
|
|
8258
8469
|
for (let a = 0; a < e.length; a++) {
|
|
8259
8470
|
let o = e[a];
|
|
8260
|
-
|
|
8261
|
-
r === `"` && i ? i = !1 : r === `"` && o === `\\` ? i = !0 : o === r && (r = void 0);
|
|
8262
|
-
continue;
|
|
8263
|
-
}
|
|
8264
|
-
o === `"` || o === `'` ? r = o : o === `.` && (t.push(e.slice(n, a)), n = a + 1);
|
|
8471
|
+
r ? r === `"` && i ? i = !1 : r === `"` && o === `\\` ? i = !0 : o === r && (r = void 0) : o === `"` || o === `'` ? r = o : o === `.` && (t.push(e.slice(n, a)), n = a + 1);
|
|
8265
8472
|
}
|
|
8266
8473
|
return t.push(e.slice(n)), t;
|
|
8267
8474
|
}
|
|
@@ -9013,11 +9220,20 @@ function yo(n, i, s, c, l = !1, g = /* @__PURE__ */ new WeakSet(), _ = void 0, v
|
|
|
9013
9220
|
for (let e of s) Q(n, l, e);
|
|
9014
9221
|
}
|
|
9015
9222
|
if ((t(o) || r(o)) && m(l)) {
|
|
9016
|
-
let e = o.key.item.value, i = l.items, s = [];
|
|
9017
|
-
for (let
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
|
|
9223
|
+
let e = o.key.item.value, i = l.items, s = [], c = r(o), u = i.indexOf(o), d = i.length;
|
|
9224
|
+
if (c) for (let t = u + 1; t < i.length; t++) {
|
|
9225
|
+
let n = i[t];
|
|
9226
|
+
if (!r(n)) continue;
|
|
9227
|
+
let a = n.key.item.value;
|
|
9228
|
+
if (a.length <= e.length && F(a, e.slice(0, a.length))) {
|
|
9229
|
+
d = t;
|
|
9230
|
+
break;
|
|
9231
|
+
}
|
|
9232
|
+
}
|
|
9233
|
+
for (let [n, l] of i.entries()) {
|
|
9234
|
+
if (l === o || c && (n < u || n >= d)) continue;
|
|
9235
|
+
let i = a(l) ? l.key.value : t(l) || r(l) ? l.key.item.value : void 0;
|
|
9236
|
+
i && i.length > e.length && F(i.slice(0, e.length), e) && s.push(l);
|
|
9021
9237
|
}
|
|
9022
9238
|
for (let e of s) Q(n, l, e);
|
|
9023
9239
|
}
|
|
@@ -9403,23 +9619,18 @@ function To(e, n, i, o, s, c, l) {
|
|
|
9403
9619
|
let n = s.items.find((e) => a(e) && F(e.key.value, t.key.value));
|
|
9404
9620
|
n ? pr(e, s, n, t) : B(e, s, t, void 0);
|
|
9405
9621
|
};
|
|
9406
|
-
for (let e of m)
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
n.key.loc.end.column = n.key.loc.start.column + n.key.raw.length, n.equals += i, U(n.value, {
|
|
9419
|
-
lines: 0,
|
|
9420
|
-
columns: i
|
|
9421
|
-
}, { first_line_only: !0 }), n.loc.end.line === n.loc.start.line && (n.loc.end.column += i), h(n);
|
|
9422
|
-
}
|
|
9622
|
+
for (let e of m) if (a(e)) h(e);
|
|
9623
|
+
else if (t(e)) {
|
|
9624
|
+
let t = e.key.item.value;
|
|
9625
|
+
for (let n of e.items) {
|
|
9626
|
+
if (!a(n)) continue;
|
|
9627
|
+
let e = n.key.raw, r = t.concat(n.key.value);
|
|
9628
|
+
n.key.value = r, n.key.raw = Ur(r, o.escapeSequenceUpperCase).raw;
|
|
9629
|
+
let i = n.key.raw.length - e.length;
|
|
9630
|
+
n.key.loc.end.column = n.key.loc.start.column + n.key.raw.length, n.equals += i, U(n.value, {
|
|
9631
|
+
lines: 0,
|
|
9632
|
+
columns: i
|
|
9633
|
+
}, { first_line_only: !0 }), n.loc.end.line === n.loc.start.line && (n.loc.end.column += i), h(n);
|
|
9423
9634
|
}
|
|
9424
9635
|
}
|
|
9425
9636
|
return;
|
|
@@ -9438,51 +9649,51 @@ function To(e, n, i, o, s, c, l) {
|
|
|
9438
9649
|
if (o) {
|
|
9439
9650
|
for (let t of i.items) B(e, o, t, void 0);
|
|
9440
9651
|
v ||= o;
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
}
|
|
9652
|
+
} else {
|
|
9653
|
+
if (a(i) && f(i.value)) {
|
|
9654
|
+
let o = i.key.value, s = e.items.find((e) => t(e) && F(e.key.item.value, o));
|
|
9655
|
+
if (s) {
|
|
9656
|
+
for (let t of i.value.items) B(e, s, d(t) ? t.item : t, void 0);
|
|
9657
|
+
v ||= s;
|
|
9658
|
+
continue;
|
|
9659
|
+
}
|
|
9660
|
+
if ($(e, o).filter((e) => a(e) || t(e) || r(e)).length > 0) {
|
|
9661
|
+
for (let t of i.value.items) {
|
|
9662
|
+
let r = d(t) ? t.item : t;
|
|
9663
|
+
if (a(r)) {
|
|
9664
|
+
let e = r.key.raw, t = o.concat(r.key.value);
|
|
9665
|
+
r.key.value = t, r.key.raw = t.map((e) => A.test(e) ? e : JSON.stringify(e).replace(/\x7f/g, `\\u007f`)).join(`.`);
|
|
9666
|
+
let n = r.key.raw.length - e.length;
|
|
9667
|
+
r.key.loc.end.column = r.key.loc.start.column + r.key.raw.length, r.equals += n, U(r.value, {
|
|
9668
|
+
lines: 0,
|
|
9669
|
+
columns: n
|
|
9670
|
+
}, { first_line_only: !0 }), r.loc.end.line === r.loc.start.line && (r.loc.end.column += n);
|
|
9671
|
+
}
|
|
9672
|
+
B(e, e, r, n === 0 ? _ : void 0), v ||= r;
|
|
9461
9673
|
}
|
|
9462
|
-
|
|
9674
|
+
continue;
|
|
9463
9675
|
}
|
|
9464
|
-
continue;
|
|
9465
9676
|
}
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
|
|
9472
|
-
|
|
9473
|
-
|
|
9474
|
-
|
|
9475
|
-
|
|
9476
|
-
|
|
9477
|
-
|
|
9478
|
-
}
|
|
9677
|
+
if (t(i)) {
|
|
9678
|
+
let t = i.key.item.value;
|
|
9679
|
+
if ($(e, t).filter((e) => a(e)).length > 0) {
|
|
9680
|
+
for (let r of i.items) {
|
|
9681
|
+
if (a(r)) {
|
|
9682
|
+
let e = r.key.raw, n = t.concat(r.key.value);
|
|
9683
|
+
r.key.value = n, r.key.raw = n.map((e) => A.test(e) ? e : JSON.stringify(e).replace(/\x7f/g, `\\u007f`)).join(`.`);
|
|
9684
|
+
let i = r.key.raw.length - e.length;
|
|
9685
|
+
r.key.loc.end.column = r.key.loc.start.column + r.key.raw.length, r.equals += i, U(r.value, {
|
|
9686
|
+
lines: 0,
|
|
9687
|
+
columns: i
|
|
9688
|
+
}, { first_line_only: !0 }), r.loc.end.line === r.loc.start.line && (r.loc.end.column += i);
|
|
9689
|
+
}
|
|
9690
|
+
B(e, e, r, n === 0 ? _ : void 0), v ||= r;
|
|
9479
9691
|
}
|
|
9480
|
-
|
|
9692
|
+
continue;
|
|
9481
9693
|
}
|
|
9482
|
-
continue;
|
|
9483
9694
|
}
|
|
9695
|
+
n > 0 && v && t(v) && a(i) ? B(e, v, i, void 0) : B(e, e, i, n === 0 ? _ : void 0), v ||= i;
|
|
9484
9696
|
}
|
|
9485
|
-
n > 0 && v && t(v) && a(i) ? B(e, v, i, void 0) : B(e, e, i, n === 0 ? _ : void 0), v ||= i;
|
|
9486
9697
|
}
|
|
9487
9698
|
let y = v;
|
|
9488
9699
|
t(y) && l.add(y), c.add(y);
|
|
@@ -9665,11 +9876,26 @@ function Ko(e, t) {
|
|
|
9665
9876
|
temporal: (t == null ? void 0 : t.temporal) ?? !1
|
|
9666
9877
|
});
|
|
9667
9878
|
}
|
|
9668
|
-
|
|
9669
|
-
//#region src/shared/codex-config.ts
|
|
9670
|
-
const HOOK_COMMAND = "harnessforce hook session-start";
|
|
9671
|
-
const HOOK_COMMAND_WINDOWS = "harnessforce.cmd hook session-start";
|
|
9879
|
+
// src/shared/codex-config.ts
|
|
9672
9880
|
const HOOK_TIMEOUT_SECONDS = 10;
|
|
9881
|
+
const HOOKS = [
|
|
9882
|
+
{
|
|
9883
|
+
event: "SessionStart",
|
|
9884
|
+
subcommand: "session-start"
|
|
9885
|
+
},
|
|
9886
|
+
{
|
|
9887
|
+
event: "PostToolUse",
|
|
9888
|
+
subcommand: "post-tool-use",
|
|
9889
|
+
matcher: "^apply_patch$",
|
|
9890
|
+
isAsync: true
|
|
9891
|
+
},
|
|
9892
|
+
{
|
|
9893
|
+
event: "Stop",
|
|
9894
|
+
subcommand: "stop",
|
|
9895
|
+
isAsync: true
|
|
9896
|
+
}
|
|
9897
|
+
];
|
|
9898
|
+
const hookCommand = (spec) => `harnessforce hook ${spec.subcommand}`;
|
|
9673
9899
|
const FILE_MODE$2 = 384;
|
|
9674
9900
|
function codexConfigPath(env, homeDir) {
|
|
9675
9901
|
return join(absoluteEnv(env.CODEX_HOME) ?? join(homeDir, ".codex"), "config.toml");
|
|
@@ -9705,11 +9931,14 @@ function withHarnessforce(config, input) {
|
|
|
9705
9931
|
headers: { Authorization: `Bearer ${input.ingestKey}` },
|
|
9706
9932
|
protocol: "binary"
|
|
9707
9933
|
} };
|
|
9708
|
-
|
|
9934
|
+
otel.log_user_prompt = input.logUserPrompt;
|
|
9935
|
+
otel.log_agent_responses = input.logAgentResponses;
|
|
9709
9936
|
const hooks = tableAt(config, "hooks");
|
|
9710
|
-
const
|
|
9711
|
-
|
|
9712
|
-
|
|
9937
|
+
for (const spec of HOOKS) {
|
|
9938
|
+
const groups = hooks[spec.event] ?? [];
|
|
9939
|
+
if (!Array.isArray(groups)) throw new Error(spec.event);
|
|
9940
|
+
hooks[spec.event] = withHandler(groups, spec);
|
|
9941
|
+
}
|
|
9713
9942
|
const servers = tableAt(config, "mcp_servers");
|
|
9714
9943
|
const existing = servers.harnessforce;
|
|
9715
9944
|
if (existing !== void 0 && !isObject(existing)) throw new Error("server");
|
|
@@ -9725,16 +9954,21 @@ function tableAt(parent, key) {
|
|
|
9725
9954
|
parent[key] = value;
|
|
9726
9955
|
return value;
|
|
9727
9956
|
}
|
|
9728
|
-
|
|
9729
|
-
const
|
|
9730
|
-
|
|
9957
|
+
function withHandler(groups, spec) {
|
|
9958
|
+
const command = hookCommand(spec);
|
|
9959
|
+
const isOurs = (entry) => isObject(entry) && entry.command === command;
|
|
9960
|
+
const hasOurs = (group) => isObject(group) && Array.isArray(group.hooks) && group.hooks.some(isOurs);
|
|
9731
9961
|
const handler = {
|
|
9732
9962
|
type: "command",
|
|
9733
|
-
command
|
|
9734
|
-
commandWindows:
|
|
9735
|
-
timeout: HOOK_TIMEOUT_SECONDS
|
|
9736
|
-
|
|
9737
|
-
|
|
9963
|
+
command,
|
|
9964
|
+
commandWindows: `harnessforce.cmd hook ${spec.subcommand}`,
|
|
9965
|
+
timeout: HOOK_TIMEOUT_SECONDS,
|
|
9966
|
+
...spec.isAsync ? { async: true } : {}
|
|
9967
|
+
};
|
|
9968
|
+
if (!groups.some(hasOurs)) return [...groups, {
|
|
9969
|
+
...spec.matcher === void 0 ? {} : { matcher: spec.matcher },
|
|
9970
|
+
hooks: [handler]
|
|
9971
|
+
}];
|
|
9738
9972
|
return groups.map((group) => hasOurs(group) ? {
|
|
9739
9973
|
...group,
|
|
9740
9974
|
hooks: group.hooks.map((entry) => isOurs(entry) ? handler : entry)
|
|
@@ -9754,8 +9988,29 @@ async function writeCodexConfig(path, text) {
|
|
|
9754
9988
|
await rm(temporary, { force: true });
|
|
9755
9989
|
}
|
|
9756
9990
|
}
|
|
9757
|
-
|
|
9758
|
-
|
|
9991
|
+
// src/init/credentials-api.ts
|
|
9992
|
+
const VISIBILITIES = /* @__PURE__ */ new Set([
|
|
9993
|
+
"all_members",
|
|
9994
|
+
"owners_admins_and_sender",
|
|
9995
|
+
"sender_only"
|
|
9996
|
+
]);
|
|
9997
|
+
const RETENTION_DAYS = /* @__PURE__ */ new Set([
|
|
9998
|
+
7,
|
|
9999
|
+
14,
|
|
10000
|
+
30
|
|
10001
|
+
]);
|
|
10002
|
+
function parseContent(value) {
|
|
10003
|
+
if (!isObject(value)) return void 0;
|
|
10004
|
+
const { prompts, model_and_tools: modelAndTools, visibility } = value;
|
|
10005
|
+
const retentionDays = value.retention_days;
|
|
10006
|
+
if (typeof prompts !== "boolean" || typeof modelAndTools !== "boolean" || typeof visibility !== "string" || !VISIBILITIES.has(visibility) || typeof retentionDays !== "number" || !RETENTION_DAYS.has(retentionDays)) return void 0;
|
|
10007
|
+
return {
|
|
10008
|
+
isPromptsStored: prompts,
|
|
10009
|
+
isModelAndToolsStored: modelAndTools,
|
|
10010
|
+
visibility,
|
|
10011
|
+
retentionDays
|
|
10012
|
+
};
|
|
10013
|
+
}
|
|
9759
10014
|
const WORKSPACE_ID = /^[A-Za-z0-9-]{1,128}$/;
|
|
9760
10015
|
const NO_WHITESPACE = /^\S+$/;
|
|
9761
10016
|
const LIMIT_ROLES = /* @__PURE__ */ new Set([
|
|
@@ -9764,15 +10019,16 @@ const LIMIT_ROLES = /* @__PURE__ */ new Set([
|
|
|
9764
10019
|
"member"
|
|
9765
10020
|
]);
|
|
9766
10021
|
function parseIssued(body) {
|
|
9767
|
-
const { workspace_id: workspaceId, ingest_key: ingestKey, ingest_endpoint: ingestEndpoint,
|
|
9768
|
-
if (typeof workspaceId !== "string" || typeof ingestKey !== "string" || typeof ingestEndpoint !== "string" ||
|
|
10022
|
+
const { workspace_id: workspaceId, ingest_key: ingestKey, ingest_endpoint: ingestEndpoint, content } = body;
|
|
10023
|
+
if (typeof workspaceId !== "string" || typeof ingestKey !== "string" || typeof ingestEndpoint !== "string" || !WORKSPACE_ID.test(workspaceId)) return void 0;
|
|
9769
10024
|
const apiToken = parseTokenPair(body, workspaceId);
|
|
9770
|
-
|
|
10025
|
+
const parsedContent = parseContent(content);
|
|
10026
|
+
return ingestKey.startsWith(`hf_ik_${workspaceId}_`) && NO_WHITESPACE.test(ingestKey) && apiToken !== void 0 && parsedContent !== void 0 && parseAllowedUrl(ingestEndpoint) !== void 0 && apiToken && parsedContent ? {
|
|
9771
10027
|
workspaceId,
|
|
9772
10028
|
ingestKey,
|
|
9773
10029
|
apiToken,
|
|
9774
10030
|
ingestEndpoint,
|
|
9775
|
-
|
|
10031
|
+
content: parsedContent
|
|
9776
10032
|
} : void 0;
|
|
9777
10033
|
}
|
|
9778
10034
|
function classify(status, body) {
|
|
@@ -9812,8 +10068,7 @@ async function requestCredentials(base, request, fetchImpl) {
|
|
|
9812
10068
|
return { kind: "unexpected" };
|
|
9813
10069
|
}
|
|
9814
10070
|
}
|
|
9815
|
-
|
|
9816
|
-
//#region src/init/pkce.ts
|
|
10071
|
+
// src/init/pkce.ts
|
|
9817
10072
|
const RANDOM_BYTES = 32;
|
|
9818
10073
|
const codeChallenge = (codeVerifier) => createHash("sha256").update(codeVerifier).digest("base64url");
|
|
9819
10074
|
function createLoginSecrets() {
|
|
@@ -9824,8 +10079,7 @@ function createLoginSecrets() {
|
|
|
9824
10079
|
state: randomBytes(RANDOM_BYTES).toString("base64url")
|
|
9825
10080
|
};
|
|
9826
10081
|
}
|
|
9827
|
-
|
|
9828
|
-
//#region src/init/init.ts
|
|
10082
|
+
// src/init/init.ts
|
|
9829
10083
|
const CLIENT_ID = "harnessforce-cli";
|
|
9830
10084
|
const DEFAULT_PORT = 8080;
|
|
9831
10085
|
const MAX_REVOKE_HASHES = 100;
|
|
@@ -9856,28 +10110,43 @@ async function runInit(args, deps) {
|
|
|
9856
10110
|
if (settings.kind === "invalid" || !hasWritableMarketplaces(settings.settings)) stop$1("settingsUnreadable");
|
|
9857
10111
|
const authorizationEndpoint = await discoverAuthorizationEndpoint(base, deps.fetch) ?? stop$1("network");
|
|
9858
10112
|
const secrets = createLoginSecrets();
|
|
9859
|
-
const { code, redirectUri } = await logIn(authorizationEndpoint, secrets, port, deps);
|
|
9860
|
-
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
codexPath,
|
|
9877
|
-
|
|
9878
|
-
|
|
9879
|
-
|
|
9880
|
-
|
|
10113
|
+
const { code, redirectUri, loopback } = await logIn(authorizationEndpoint, secrets, port, new URL(recordedUrl).origin, deps);
|
|
10114
|
+
let completion = "failed";
|
|
10115
|
+
try {
|
|
10116
|
+
const outcome = await requestCredentials(base, {
|
|
10117
|
+
code,
|
|
10118
|
+
code_verifier: secrets.codeVerifier,
|
|
10119
|
+
client_id: CLIENT_ID,
|
|
10120
|
+
redirect_uri: redirectUri,
|
|
10121
|
+
revoke_key_hashes: revoke.keyHashes,
|
|
10122
|
+
revoke_api_token_hashes: revoke.apiTokenHashes
|
|
10123
|
+
}, deps.fetch);
|
|
10124
|
+
if (outcome.kind === "viewer") completion = "viewer";
|
|
10125
|
+
const issued = issuedOrStop(outcome);
|
|
10126
|
+
const isPluginAutoUpdateOn = await save(issued, recordedUrl, settingsPath, args, store).catch((error) => {
|
|
10127
|
+
if (isStop(error)) throw error;
|
|
10128
|
+
return stop$1("saveFailed");
|
|
10129
|
+
});
|
|
10130
|
+
const codexPath = codexConfigPath(deps.env, deps.homeDir);
|
|
10131
|
+
await saveCodexConfig(issued, recordedUrl, codexPath, args.sendContent).catch(() => stop$1("codexSaveFailed"));
|
|
10132
|
+
completion = "connected";
|
|
10133
|
+
loopback.finish({
|
|
10134
|
+
status: completion,
|
|
10135
|
+
workspaceId: issued.workspaceId
|
|
10136
|
+
});
|
|
10137
|
+
return finishInit(issued, args, deps, {
|
|
10138
|
+
codexPath,
|
|
10139
|
+
connection: recordedUrl,
|
|
10140
|
+
isKernelKeyring: store.isKernelKeyring,
|
|
10141
|
+
isPluginAutoUpdateOn
|
|
10142
|
+
});
|
|
10143
|
+
} finally {
|
|
10144
|
+
loopback.finish({ status: completion });
|
|
10145
|
+
}
|
|
10146
|
+
}
|
|
10147
|
+
function finishInit(issued, args, deps, result) {
|
|
10148
|
+
deps.stdout(`${contentNotice(issued.content, args.sendContent)}\n`);
|
|
10149
|
+
return result;
|
|
9881
10150
|
}
|
|
9882
10151
|
async function openKeychain(deps, previousWorkspaceId) {
|
|
9883
10152
|
return await deps.keychain.openForInit({
|
|
@@ -9899,11 +10168,12 @@ function revokeHashes({ items }) {
|
|
|
9899
10168
|
apiTokenHashes
|
|
9900
10169
|
};
|
|
9901
10170
|
}
|
|
9902
|
-
async function logIn(authorizationEndpoint, secrets, port, deps) {
|
|
10171
|
+
async function logIn(authorizationEndpoint, secrets, port, webOrigin, deps) {
|
|
9903
10172
|
const loopback = await deps.startLoopback({
|
|
9904
10173
|
state: secrets.state,
|
|
9905
10174
|
timeoutMs: deps.callbackTimeoutMs,
|
|
9906
|
-
port
|
|
10175
|
+
port,
|
|
10176
|
+
webOrigin
|
|
9907
10177
|
}).catch(() => stopWith(listenFailedMessage(port)));
|
|
9908
10178
|
const authorization = new URL(authorizationEndpoint);
|
|
9909
10179
|
for (const [name, value] of Object.entries({
|
|
@@ -9915,10 +10185,23 @@ async function logIn(authorizationEndpoint, secrets, port, deps) {
|
|
|
9915
10185
|
state: secrets.state
|
|
9916
10186
|
})) authorization.searchParams.set(name, value);
|
|
9917
10187
|
if (!await deps.openBrowser(authorization.href)) deps.stderr(`${authorization.href}\n`);
|
|
9918
|
-
|
|
9919
|
-
|
|
9920
|
-
|
|
9921
|
-
|
|
10188
|
+
const callback = await loopback.callback ?? stop$1("timeout");
|
|
10189
|
+
try {
|
|
10190
|
+
return {
|
|
10191
|
+
code: codeOrStop(callback, secrets.state),
|
|
10192
|
+
redirectUri: loopback.redirectUri,
|
|
10193
|
+
loopback
|
|
10194
|
+
};
|
|
10195
|
+
} catch (error) {
|
|
10196
|
+
loopback.finish({ status: callbackFailure(callback, secrets.state) });
|
|
10197
|
+
throw error;
|
|
10198
|
+
}
|
|
10199
|
+
}
|
|
10200
|
+
function callbackFailure(callback, state) {
|
|
10201
|
+
if (callback.state !== state) return "failed";
|
|
10202
|
+
if (callback.error === "access_denied") return "denied";
|
|
10203
|
+
if (callback.error === "no_workspace") return "no_workspace";
|
|
10204
|
+
return "failed";
|
|
9922
10205
|
}
|
|
9923
10206
|
function codeOrStop(callback, state) {
|
|
9924
10207
|
if (callback.state !== state) return stop$1("loginFailed");
|
|
@@ -9961,21 +10244,35 @@ async function save(issued, connection, settingsPath, args, store) {
|
|
|
9961
10244
|
OTEL_TRACES_EXPORTER: "otlp",
|
|
9962
10245
|
OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf"
|
|
9963
10246
|
};
|
|
9964
|
-
|
|
10247
|
+
Object.assign(env, contentEnv(issued.content, args.sendContent));
|
|
9965
10248
|
const { settings, isOn } = withPluginAutoUpdate(mergeUserSettings(current.settings, env), args.noPluginAutoUpdate);
|
|
9966
10249
|
await writeUserSettings(settingsPath, settings);
|
|
9967
10250
|
return isOn;
|
|
9968
10251
|
}
|
|
10252
|
+
function contentEnv(content, sendContent) {
|
|
10253
|
+
const flag = (isStored) => sendContent && isStored ? "1" : "0";
|
|
10254
|
+
const modelAndTools = flag(content.isModelAndToolsStored);
|
|
10255
|
+
return {
|
|
10256
|
+
OTEL_LOG_USER_PROMPTS: flag(content.isPromptsStored),
|
|
10257
|
+
OTEL_LOG_ASSISTANT_RESPONSES: modelAndTools,
|
|
10258
|
+
OTEL_LOG_TOOL_DETAILS: modelAndTools,
|
|
10259
|
+
OTEL_LOG_TOOL_CONTENT: modelAndTools
|
|
10260
|
+
};
|
|
10261
|
+
}
|
|
10262
|
+
function contentNotice(content, sendContent) {
|
|
10263
|
+
if (!sendContent) return INIT_MESSAGES.contentNotSent;
|
|
10264
|
+
return content.isPromptsStored || content.isModelAndToolsStored ? contentEnabledMessage(content) : INIT_MESSAGES.contentNotStored;
|
|
10265
|
+
}
|
|
9969
10266
|
async function saveCodexConfig(issued, connection, path, sendContent) {
|
|
9970
10267
|
await writeCodexConfig(path, mergeCodexConfig(await readCodexConfig(path), {
|
|
9971
10268
|
connection,
|
|
9972
10269
|
ingestEndpoint: issued.ingestEndpoint,
|
|
9973
10270
|
ingestKey: issued.ingestKey,
|
|
9974
|
-
logUserPrompt: sendContent && issued.
|
|
10271
|
+
logUserPrompt: sendContent && issued.content.isPromptsStored,
|
|
10272
|
+
logAgentResponses: sendContent && issued.content.isModelAndToolsStored
|
|
9975
10273
|
}));
|
|
9976
10274
|
}
|
|
9977
|
-
|
|
9978
|
-
//#region src/otel-headers.ts
|
|
10275
|
+
// src/otel-headers.ts
|
|
9979
10276
|
const DESTINATION_VARIABLES = [
|
|
9980
10277
|
"HARNESSFORCE_ENDPOINT",
|
|
9981
10278
|
"OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
@@ -10029,8 +10326,7 @@ async function selectKey(env, managedDir, unlimited) {
|
|
|
10029
10326
|
function isPinnedDestination(env, pinnedOrigin) {
|
|
10030
10327
|
return DESTINATION_VARIABLES.map((name) => env[name]).filter((value) => Boolean(value)).every((value) => parseAllowedUrl(value)?.origin === pinnedOrigin);
|
|
10031
10328
|
}
|
|
10032
|
-
|
|
10033
|
-
//#region ../agent-core/src/config/plugins.ts
|
|
10329
|
+
// ../agent-core/src/config/plugins.ts
|
|
10034
10330
|
const NON_MARKETPLACE_ORIGINS = /* @__PURE__ */ new Set([
|
|
10035
10331
|
"inline",
|
|
10036
10332
|
"skills-dir",
|
|
@@ -10071,8 +10367,7 @@ async function collectPlugins(pluginsRoot, settingsByPrecedence, scope, guard) {
|
|
|
10071
10367
|
collectMcpServers$1(s, (await readJsonObjectFile(join(root, ".mcp.json"), guard))?.mcpServers);
|
|
10072
10368
|
}
|
|
10073
10369
|
}
|
|
10074
|
-
|
|
10075
|
-
//#region ../agent-core/src/config/collect.ts
|
|
10370
|
+
// ../agent-core/src/config/collect.ts
|
|
10076
10371
|
const COLLECT_BUDGET_MS = 1e3;
|
|
10077
10372
|
async function readManagedSettings(managedDir, guard) {
|
|
10078
10373
|
let merged = await readJsonObjectFile(join(managedDir, "managed-settings.json"), guard);
|
|
@@ -10206,8 +10501,7 @@ async function collectConfig(options) {
|
|
|
10206
10501
|
components: sortComponents(sink.components)
|
|
10207
10502
|
};
|
|
10208
10503
|
}
|
|
10209
|
-
|
|
10210
|
-
//#region src/run/context.ts
|
|
10504
|
+
// src/run/context.ts
|
|
10211
10505
|
async function resolveConfigVersion(options) {
|
|
10212
10506
|
const startedMs = options.now().getTime();
|
|
10213
10507
|
const result = await collectConfig({
|
|
@@ -10227,8 +10521,7 @@ async function resolveLaunchContext(options) {
|
|
|
10227
10521
|
...configVersion === void 0 ? {} : { configVersion }
|
|
10228
10522
|
};
|
|
10229
10523
|
}
|
|
10230
|
-
|
|
10231
|
-
//#region src/run/issues.ts
|
|
10524
|
+
// src/run/issues.ts
|
|
10232
10525
|
const MAX_CANDIDATES = 10;
|
|
10233
10526
|
const DOT_SEGMENT = /^\.{1,2}$/;
|
|
10234
10527
|
async function get(url, apiToken, fetchImpl, timeoutMs) {
|
|
@@ -10287,8 +10580,7 @@ async function resolveIssue(base, identifier, apiToken, fetchImpl, timeoutMs = R
|
|
|
10287
10580
|
}
|
|
10288
10581
|
return fetchCandidates(base, identifier, apiToken, fetchImpl, timeoutMs);
|
|
10289
10582
|
}
|
|
10290
|
-
|
|
10291
|
-
//#region src/run/launch.ts
|
|
10583
|
+
// src/run/launch.ts
|
|
10292
10584
|
const STRIPPED_FROM_SHELL = /* @__PURE__ */ new Set(["OTEL_EXPORTER_OTLP_HEADERS", ...[
|
|
10293
10585
|
"TRACES",
|
|
10294
10586
|
"LOGS",
|
|
@@ -10350,8 +10642,7 @@ function buildLaunch(request) {
|
|
|
10350
10642
|
...isClaudeCode(request.agent) ? { settingsEnv: injectedEnv } : {}
|
|
10351
10643
|
};
|
|
10352
10644
|
}
|
|
10353
|
-
|
|
10354
|
-
//#region src/run/messages.ts
|
|
10645
|
+
// src/run/messages.ts
|
|
10355
10646
|
const RUN_MESSAGES = {
|
|
10356
10647
|
keychainUnavailable: INIT_MESSAGES.keychainUnavailable,
|
|
10357
10648
|
initRequired: "Run `harnessforce init`",
|
|
@@ -10364,22 +10655,21 @@ const RUN_MESSAGES = {
|
|
|
10364
10655
|
candidatesHeader: "Could not resolve the Issue. Candidates:"
|
|
10365
10656
|
};
|
|
10366
10657
|
const launchFailedMessage = (agent) => `Could not start ${agent}`;
|
|
10367
|
-
|
|
10368
|
-
//#region src/run/run.ts
|
|
10658
|
+
// src/run/run.ts
|
|
10369
10659
|
const stop = (message) => stopWith(RUN_MESSAGES[message]);
|
|
10370
10660
|
const printable = (text) => text.replace(/\p{Cc}/gu, "");
|
|
10371
10661
|
function candidateList(candidates) {
|
|
10372
10662
|
const lines = candidates.map(({ identifier, title }) => `${printable(identifier)} ${printable(title)}`);
|
|
10373
10663
|
return [RUN_MESSAGES.candidatesHeader, ...lines].join("\n");
|
|
10374
10664
|
}
|
|
10375
|
-
const ACCESS_MESSAGES$
|
|
10665
|
+
const ACCESS_MESSAGES$2 = {
|
|
10376
10666
|
keychainUnavailable: RUN_MESSAGES.keychainUnavailable,
|
|
10377
10667
|
initRequired: RUN_MESSAGES.initRequired,
|
|
10378
10668
|
invalidUrl: RUN_MESSAGES.invalidUrl,
|
|
10379
10669
|
apiTokenMissing: RUN_MESSAGES.issueInitRequired
|
|
10380
10670
|
};
|
|
10381
10671
|
async function prepareLaunch(args, deps) {
|
|
10382
|
-
const { workspaceId, ingestEndpoint, readApiBase, accessToken } = await verifyCliAccess(deps, ACCESS_MESSAGES$
|
|
10672
|
+
const { workspaceId, ingestEndpoint, readApiBase, accessToken } = await verifyCliAccess(deps, ACCESS_MESSAGES$2);
|
|
10383
10673
|
if (!isIssueIdentifier(args.issue)) stop("invalidIssue");
|
|
10384
10674
|
const readFetch = createApiTokenSession({
|
|
10385
10675
|
keychain: deps.keychain,
|
|
@@ -10444,8 +10734,88 @@ async function runWithoutHarnessforce(args, deps) {
|
|
|
10444
10734
|
deps.stderr(`${launchFailedMessage(args.agent)}\n`);
|
|
10445
10735
|
return 1;
|
|
10446
10736
|
}
|
|
10447
|
-
|
|
10448
|
-
|
|
10737
|
+
// src/status/command.ts
|
|
10738
|
+
const STATUS_USAGE = "Usage: harnessforce status [--json]\n";
|
|
10739
|
+
const MESSAGES = {
|
|
10740
|
+
failed: "Could not read the Harnessforce status. Run `harnessforce init` and try again.",
|
|
10741
|
+
loginExpired: "Your login has expired. Run `harnessforce init`"
|
|
10742
|
+
};
|
|
10743
|
+
const ACCESS_MESSAGES$1 = {
|
|
10744
|
+
keychainUnavailable: INIT_MESSAGES.keychainUnavailable,
|
|
10745
|
+
initRequired: MESSAGES.failed,
|
|
10746
|
+
invalidUrl: MESSAGES.failed,
|
|
10747
|
+
apiTokenMissing: MESSAGES.failed
|
|
10748
|
+
};
|
|
10749
|
+
const STEP_TITLES = {
|
|
10750
|
+
2: "Connect the GitHub App",
|
|
10751
|
+
3: "Assign repositories to a project",
|
|
10752
|
+
4: "Set up telemetry",
|
|
10753
|
+
5: "Wait for your first run"
|
|
10754
|
+
};
|
|
10755
|
+
const STATUS_MARKS = {
|
|
10756
|
+
done: "✓",
|
|
10757
|
+
todo: "・",
|
|
10758
|
+
unavailable: "?"
|
|
10759
|
+
};
|
|
10760
|
+
function unwrap(outcome) {
|
|
10761
|
+
if (outcome.kind === "ok") return outcome.value;
|
|
10762
|
+
return stopWith(outcome.kind === "unauthorized" ? MESSAGES.loginExpired : MESSAGES.failed);
|
|
10763
|
+
}
|
|
10764
|
+
async function originRepository(cwd, git) {
|
|
10765
|
+
const url = await git(cwd, [
|
|
10766
|
+
"remote",
|
|
10767
|
+
"get-url",
|
|
10768
|
+
"origin"
|
|
10769
|
+
]);
|
|
10770
|
+
return url === void 0 ? void 0 : normalizeRepository(url);
|
|
10771
|
+
}
|
|
10772
|
+
function stepLine(step) {
|
|
10773
|
+
const parts = [STATUS_MARKS[step.status], STEP_TITLES[step.step]];
|
|
10774
|
+
if (step.status === "todo") parts.push(step.url);
|
|
10775
|
+
return parts.join(" ");
|
|
10776
|
+
}
|
|
10777
|
+
async function runStatus(json, deps) {
|
|
10778
|
+
const access = await verifyCliAccess(deps, ACCESS_MESSAGES$1);
|
|
10779
|
+
const readFetch = createApiTokenSession({
|
|
10780
|
+
keychain: deps.keychain,
|
|
10781
|
+
workspaceId: access.workspaceId,
|
|
10782
|
+
readBase: access.readApiBase,
|
|
10783
|
+
fetch: deps.fetch,
|
|
10784
|
+
now: deps.now,
|
|
10785
|
+
sleep: deps.sleep,
|
|
10786
|
+
homeDir: deps.homeDir
|
|
10787
|
+
}).authorizedFetch(deps.fetch);
|
|
10788
|
+
const onboarding = unwrap(await fetchOnboarding(access.readApiBase, access.accessToken, readFetch));
|
|
10789
|
+
const name = await originRepository(deps.cwd, deps.git);
|
|
10790
|
+
const repository = name === void 0 ? null : {
|
|
10791
|
+
name,
|
|
10792
|
+
connected: unwrap(await listConnectedRepositories(access.readApiBase, access.accessToken, readFetch)).has(name)
|
|
10793
|
+
};
|
|
10794
|
+
if (json) {
|
|
10795
|
+
deps.stdout(`${JSON.stringify({
|
|
10796
|
+
...onboarding,
|
|
10797
|
+
repository
|
|
10798
|
+
})}\n`);
|
|
10799
|
+
return 0;
|
|
10800
|
+
}
|
|
10801
|
+
const lines = [...onboarding.admin_steps ?? [], ...onboarding.steps].sort((a, b) => a.step - b.step).map(stepLine);
|
|
10802
|
+
const repositoryLine = repository && `This repository (${repository.name}) is ${repository.connected ? "connected" : "not connected"} to the GitHub App`;
|
|
10803
|
+
deps.stdout(`${[
|
|
10804
|
+
`${onboarding.workspace_name} (${onboarding.role})`,
|
|
10805
|
+
...lines,
|
|
10806
|
+
...repositoryLine ? [repositoryLine] : []
|
|
10807
|
+
].join("\n")}\n`);
|
|
10808
|
+
return 0;
|
|
10809
|
+
}
|
|
10810
|
+
function statusCommand(args, deps) {
|
|
10811
|
+
const json = args.length === 1 && args[0] === "--json";
|
|
10812
|
+
if (args.length > 0 && !json) {
|
|
10813
|
+
deps.stderr(STATUS_USAGE);
|
|
10814
|
+
return Promise.resolve(1);
|
|
10815
|
+
}
|
|
10816
|
+
return runUntilStop(() => runStatus(json, deps), deps.stderr);
|
|
10817
|
+
}
|
|
10818
|
+
// src/top/args.ts
|
|
10449
10819
|
const TOP_USAGE = "Usage: harnessforce top [--once] [--json] [--ascii] [--theme <auto|light|dark|ansi>]\n";
|
|
10450
10820
|
const THEMES = [
|
|
10451
10821
|
"auto",
|
|
@@ -10474,8 +10844,7 @@ function parseTopArgs(args) {
|
|
|
10474
10844
|
theme
|
|
10475
10845
|
};
|
|
10476
10846
|
}
|
|
10477
|
-
|
|
10478
|
-
//#region src/top/width.ts
|
|
10847
|
+
// src/top/width.ts
|
|
10479
10848
|
const SGR = /\x1b\[[0-9;]*m/g;
|
|
10480
10849
|
const stripSgr = (text) => text.replace(SGR, "");
|
|
10481
10850
|
const WIDE_RANGES = [
|
|
@@ -10540,8 +10909,7 @@ function truncate(text, maxWidth, ambiguousWide, ellipsis) {
|
|
|
10540
10909
|
function padEnd(text, width, ambiguousWide) {
|
|
10541
10910
|
return text + " ".repeat(Math.max(0, width - displayWidth(text, ambiguousWide)));
|
|
10542
10911
|
}
|
|
10543
|
-
|
|
10544
|
-
//#region src/top/frame.ts
|
|
10912
|
+
// src/top/frame.ts
|
|
10545
10913
|
const plainPainter = {
|
|
10546
10914
|
color: (_token, text) => text,
|
|
10547
10915
|
bold: (text) => text,
|
|
@@ -10741,8 +11109,7 @@ function renderDetail(row, options) {
|
|
|
10741
11109
|
];
|
|
10742
11110
|
}
|
|
10743
11111
|
const skippedNotice = (snapshot) => snapshot.skippedLines > 0 || snapshot.skippedFiles > 0 ? [`Skipped ${snapshot.skippedLines} unreadable line(s) and ${snapshot.skippedFiles} unreadable file(s)`] : [];
|
|
10744
|
-
|
|
10745
|
-
//#region src/top/codex-session.ts
|
|
11112
|
+
// src/top/codex-session.ts
|
|
10746
11113
|
const DETACHED = "HEAD";
|
|
10747
11114
|
const SHELL = "shell";
|
|
10748
11115
|
const COUNTED_CALLS = /* @__PURE__ */ new Set(["function_call", "custom_tool_call"]);
|
|
@@ -10860,8 +11227,7 @@ var CodexSessionAccumulator = class {
|
|
|
10860
11227
|
};
|
|
10861
11228
|
}
|
|
10862
11229
|
};
|
|
10863
|
-
|
|
10864
|
-
//#region src/top/reader.ts
|
|
11230
|
+
// src/top/reader.ts
|
|
10865
11231
|
const WINDOW_MS = 864e5;
|
|
10866
11232
|
const freshState = (newAccumulator) => ({
|
|
10867
11233
|
offset: 0,
|
|
@@ -10986,8 +11352,7 @@ function createTopReader({ projectsDir, codexSessionsDir, git }) {
|
|
|
10986
11352
|
};
|
|
10987
11353
|
} };
|
|
10988
11354
|
}
|
|
10989
|
-
|
|
10990
|
-
//#region src/top/color.ts
|
|
11355
|
+
// src/top/color.ts
|
|
10991
11356
|
const MIN_CONTRAST = 3;
|
|
10992
11357
|
const TOKEN_VALUES = {
|
|
10993
11358
|
good: [[
|
|
@@ -11126,8 +11491,7 @@ function createPainter(input) {
|
|
|
11126
11491
|
dim: (text) => open$1("2", text, "22")
|
|
11127
11492
|
};
|
|
11128
11493
|
}
|
|
11129
|
-
|
|
11130
|
-
//#region src/top/screen.ts
|
|
11494
|
+
// src/top/screen.ts
|
|
11131
11495
|
const ENTER = "\x1B[?1049h\x1B[?25l";
|
|
11132
11496
|
const RESTORE = "\x1B[?25h\x1B[?1049l";
|
|
11133
11497
|
const HOME = "\x1B[H";
|
|
@@ -11237,7 +11601,7 @@ async function runScreen(options) {
|
|
|
11237
11601
|
};
|
|
11238
11602
|
const indexOfSelected = (rows) => {
|
|
11239
11603
|
const index = rows.findIndex((row) => row.sessionId === selectedId);
|
|
11240
|
-
return index
|
|
11604
|
+
return Math.max(index, 0);
|
|
11241
11605
|
};
|
|
11242
11606
|
const select = (rows, index) => {
|
|
11243
11607
|
selectedId = rows[Math.max(0, Math.min(index, rows.length - 1))]?.sessionId;
|
|
@@ -11347,8 +11711,7 @@ async function runScreen(options) {
|
|
|
11347
11711
|
render();
|
|
11348
11712
|
});
|
|
11349
11713
|
}
|
|
11350
|
-
|
|
11351
|
-
//#region src/top/settings.ts
|
|
11714
|
+
// src/top/settings.ts
|
|
11352
11715
|
const DEFAULT_SETTINGS = {
|
|
11353
11716
|
ambiguousWide: false,
|
|
11354
11717
|
braille: false
|
|
@@ -11383,8 +11746,7 @@ async function readTopSettings(homeDir) {
|
|
|
11383
11746
|
braille: braille === true
|
|
11384
11747
|
} };
|
|
11385
11748
|
}
|
|
11386
|
-
|
|
11387
|
-
//#region src/top/command.ts
|
|
11749
|
+
// src/top/command.ts
|
|
11388
11750
|
const iso$1 = (ms) => new Date(ms).toISOString();
|
|
11389
11751
|
function toJson(row, nowMs) {
|
|
11390
11752
|
return {
|
|
@@ -11447,8 +11809,7 @@ async function topCommand(args, deps) {
|
|
|
11447
11809
|
deps.stdout(`${lines.join("\n")}\n`);
|
|
11448
11810
|
return 0;
|
|
11449
11811
|
}
|
|
11450
|
-
|
|
11451
|
-
//#region src/tune/mcp-config.ts
|
|
11812
|
+
// src/tune/mcp-config.ts
|
|
11452
11813
|
const UNLISTED = "unlisted";
|
|
11453
11814
|
const PLUGIN_SERVER = /^([A-Za-z0-9._-]+)@[A-Za-z0-9._-]+:(.+)$/;
|
|
11454
11815
|
const toToolName = (name) => name.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
@@ -11468,8 +11829,7 @@ function mcpConfigOf(identifiers) {
|
|
|
11468
11829
|
}
|
|
11469
11830
|
};
|
|
11470
11831
|
}
|
|
11471
|
-
|
|
11472
|
-
//#region src/tune/vocabulary.ts
|
|
11832
|
+
// src/tune/vocabulary.ts
|
|
11473
11833
|
const CONTINUE_PHRASES = /* @__PURE__ */ new Set([
|
|
11474
11834
|
"continue",
|
|
11475
11835
|
"go",
|
|
@@ -11530,8 +11890,7 @@ const EDIT_TOOLS = /* @__PURE__ */ new Set([
|
|
|
11530
11890
|
"NotebookEdit"
|
|
11531
11891
|
]);
|
|
11532
11892
|
const ISSUE_TOOL = /^mcp__.+__get_issue$/;
|
|
11533
|
-
|
|
11534
|
-
//#region src/tune/analyze.ts
|
|
11893
|
+
// src/tune/analyze.ts
|
|
11535
11894
|
const ANALYZER_VERSION = "1.1.0";
|
|
11536
11895
|
const NOT_MEASURED_INTERVENTIONS = /* @__PURE__ */ new Set(["approval", "answer"]);
|
|
11537
11896
|
const emptyProposalCounts = () => Object.fromEntries(PROPOSAL_KINDS.map((kind) => [kind, {
|
|
@@ -11720,8 +12079,7 @@ function analyzeSession(input) {
|
|
|
11720
12079
|
records_skipped: input.skippedLines
|
|
11721
12080
|
};
|
|
11722
12081
|
}
|
|
11723
|
-
|
|
11724
|
-
//#region src/tune/config-snapshot.ts
|
|
12082
|
+
// src/tune/config-snapshot.ts
|
|
11725
12083
|
async function collectAt(projectRoot, deps, startedMs = deps.now()) {
|
|
11726
12084
|
const result = await collectConfig({
|
|
11727
12085
|
projectRoot,
|
|
@@ -11740,8 +12098,7 @@ async function collectMcpServers(cwd, git, deps) {
|
|
|
11740
12098
|
if (collected.kind === "fixed_failure") return [];
|
|
11741
12099
|
return [...new Set(collected.components.filter((c) => c.kind === "mcp_server").map((c) => c.id))];
|
|
11742
12100
|
}
|
|
11743
|
-
|
|
11744
|
-
//#region src/tune/applied.ts
|
|
12101
|
+
// src/tune/applied.ts
|
|
11745
12102
|
async function detectApplied(proposals, snapshot) {
|
|
11746
12103
|
const byRoot = /* @__PURE__ */ new Map();
|
|
11747
12104
|
const collect = (root) => {
|
|
@@ -11765,8 +12122,7 @@ async function detectApplied(proposals, snapshot) {
|
|
|
11765
12122
|
}
|
|
11766
12123
|
return detected;
|
|
11767
12124
|
}
|
|
11768
|
-
|
|
11769
|
-
//#region src/tune/usage.ts
|
|
12125
|
+
// src/tune/usage.ts
|
|
11770
12126
|
const USAGE_KINDS = [
|
|
11771
12127
|
"frequent_compaction",
|
|
11772
12128
|
"low_cache_reuse",
|
|
@@ -11837,8 +12193,7 @@ function modelShares(usages) {
|
|
|
11837
12193
|
subagent: shares(subagent)
|
|
11838
12194
|
};
|
|
11839
12195
|
}
|
|
11840
|
-
|
|
11841
|
-
//#region src/tune/display.ts
|
|
12196
|
+
// src/tune/display.ts
|
|
11842
12197
|
const median = (values) => {
|
|
11843
12198
|
if (values.length === 0) return null;
|
|
11844
12199
|
const sorted = [...values].sort((a, b) => a - b);
|
|
@@ -11906,8 +12261,7 @@ function localIso(ms) {
|
|
|
11906
12261
|
const pad = (n) => String(n).padStart(2, "0");
|
|
11907
12262
|
return `${new Date(ms + offsetMinutes * 6e4).toISOString().slice(0, 19)}${sign}${pad(Math.floor(absolute / 60))}:${pad(absolute % 60)}`;
|
|
11908
12263
|
}
|
|
11909
|
-
|
|
11910
|
-
//#region src/tune/followups.ts
|
|
12264
|
+
// src/tune/followups.ts
|
|
11911
12265
|
const PERIOD_MS = 12096e5;
|
|
11912
12266
|
const MIN_SESSIONS = 5;
|
|
11913
12267
|
const ratio = (numerator, denominator) => denominator === 0 ? null : numerator / denominator;
|
|
@@ -12011,8 +12365,7 @@ function computeFollowups(proposals, sessions, nowMs, rangeSinceMs) {
|
|
|
12011
12365
|
ended
|
|
12012
12366
|
};
|
|
12013
12367
|
}
|
|
12014
|
-
|
|
12015
|
-
//#region src/tune/lock.ts
|
|
12368
|
+
// src/tune/lock.ts
|
|
12016
12369
|
const LOCK_WAIT_MS = 3e4;
|
|
12017
12370
|
const STALE_LOCK_MS = 6e4;
|
|
12018
12371
|
const HEARTBEAT_MS = 1e4;
|
|
@@ -12047,8 +12400,7 @@ async function acquireTuneLock(path, deps) {
|
|
|
12047
12400
|
await deps.sleep(RETRY_INTERVAL_MS);
|
|
12048
12401
|
}
|
|
12049
12402
|
}
|
|
12050
|
-
|
|
12051
|
-
//#region src/tune/messages.ts
|
|
12403
|
+
// src/tune/messages.ts
|
|
12052
12404
|
const NOTICE_ORDER = [
|
|
12053
12405
|
"insufficient",
|
|
12054
12406
|
"outOfRange",
|
|
@@ -12096,7 +12448,7 @@ const TUNE_MESSAGES = {
|
|
|
12096
12448
|
unwritable: "Cannot write to `~/.harnessforce/tune/`, so the analysis results are not sent",
|
|
12097
12449
|
recordFailed: "Could not record the suggestion",
|
|
12098
12450
|
recordInvalid: (field) => `The suggestion record input is invalid: ${field}`,
|
|
12099
|
-
usage: "Usage: harnessforce tune [--all] [--no-send] [--show-report] [--json], harnessforce tune record, or harnessforce tune --purge",
|
|
12451
|
+
usage: "Usage: harnessforce tune [--all] [--no-send] [--show-report] [--json], harnessforce tune --report-only [--all], harnessforce tune --print-schedule, harnessforce tune record, or harnessforce tune --purge",
|
|
12100
12452
|
purged: "Deleted the analysis results, suggestions and unsent analysis results in `~/.harnessforce/tune/`",
|
|
12101
12453
|
purgeFailed: "Could not delete some files in `~/.harnessforce/tune/`"
|
|
12102
12454
|
};
|
|
@@ -12122,8 +12474,7 @@ var Notices = class {
|
|
|
12122
12474
|
return code;
|
|
12123
12475
|
}
|
|
12124
12476
|
};
|
|
12125
|
-
|
|
12126
|
-
//#region src/tune/store.ts
|
|
12477
|
+
// src/tune/store.ts
|
|
12127
12478
|
const tuneDir = (homeDir) => join(homeDir, ".harnessforce", "tune");
|
|
12128
12479
|
const tunePaths = (homeDir) => {
|
|
12129
12480
|
const dir = tuneDir(homeDir);
|
|
@@ -12169,8 +12520,7 @@ async function writeFileAtomic(path, content) {
|
|
|
12169
12520
|
}
|
|
12170
12521
|
}
|
|
12171
12522
|
const writeJsonFile = (path, value) => writeFileAtomic(path, `${JSON.stringify(value)}\n`);
|
|
12172
|
-
|
|
12173
|
-
//#region src/tune/proposals.ts
|
|
12523
|
+
// src/tune/proposals.ts
|
|
12174
12524
|
const isRecord = (value) => isObject(value) && typeof value.proposal_id === "string" && typeof value.change_type === "string" && PROPOSAL_KINDS.includes(value.change_type) && Array.isArray(value.evidence_session_ids) && (value.attribution === "pending" || value.attribution === "decided");
|
|
12175
12525
|
async function readProposals(path) {
|
|
12176
12526
|
const value = await readJsonFile(path);
|
|
@@ -12209,8 +12559,7 @@ function countProposals(proposals, sessionId) {
|
|
|
12209
12559
|
}
|
|
12210
12560
|
return counts;
|
|
12211
12561
|
}
|
|
12212
|
-
|
|
12213
|
-
//#region src/tune/saved-state.ts
|
|
12562
|
+
// src/tune/saved-state.ts
|
|
12214
12563
|
async function readDestinations(path) {
|
|
12215
12564
|
const value = await readJsonFile(path);
|
|
12216
12565
|
return isObject(value) && isObject(value.destinations) ? value.destinations : {};
|
|
@@ -12269,8 +12618,7 @@ const writeSessionConfigs = (path, configs) => writeJsonFile(path, {
|
|
|
12269
12618
|
transcript_path: config.transcriptPath
|
|
12270
12619
|
}]))
|
|
12271
12620
|
});
|
|
12272
|
-
|
|
12273
|
-
//#region src/tune/send.ts
|
|
12621
|
+
// src/tune/send.ts
|
|
12274
12622
|
const BATCH_SIZE = 100;
|
|
12275
12623
|
const READ_ONLY = "workspace_read_only";
|
|
12276
12624
|
function parseReply(body, size) {
|
|
@@ -12371,8 +12719,7 @@ async function sendReports(options) {
|
|
|
12371
12719
|
result.invalid.reasons = [...reasons];
|
|
12372
12720
|
return result;
|
|
12373
12721
|
}
|
|
12374
|
-
|
|
12375
|
-
//#region src/tune/sessions.ts
|
|
12722
|
+
// src/tune/sessions.ts
|
|
12376
12723
|
const byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
12377
12724
|
async function readSubagentUsages(transcriptPath) {
|
|
12378
12725
|
const dir = join(dirname(transcriptPath), basename(transcriptPath, ".jsonl"), "subagents");
|
|
@@ -12421,8 +12768,7 @@ async function readTuneSessions(options) {
|
|
|
12421
12768
|
}
|
|
12422
12769
|
return [...sessions.values()].sort((a, b) => Date.parse(a.session.startedAt) - Date.parse(b.session.startedAt) || byCodeUnit(a.session.sessionId, b.session.sessionId));
|
|
12423
12770
|
}
|
|
12424
|
-
|
|
12425
|
-
//#region src/tune/settings.ts
|
|
12771
|
+
// src/tune/settings.ts
|
|
12426
12772
|
const tuneConfigPath = (homeDir) => join(homeDir, ".harnessforce", "config.json");
|
|
12427
12773
|
const isMissing = (error) => error?.code === "ENOENT";
|
|
12428
12774
|
async function readSendSetting(homeDir) {
|
|
@@ -12447,8 +12793,7 @@ async function readSendSetting(homeDir) {
|
|
|
12447
12793
|
if (typeof sendReport !== "boolean") return "unreadable";
|
|
12448
12794
|
return sendReport ? "send" : "no_send";
|
|
12449
12795
|
}
|
|
12450
|
-
|
|
12451
|
-
//#region src/tune/command.ts
|
|
12796
|
+
// src/tune/command.ts
|
|
12452
12797
|
const DAY_MS = 864e5;
|
|
12453
12798
|
const DEFAULT_RANGE_DAYS = 30;
|
|
12454
12799
|
const SKIPPED_RATIO = .05;
|
|
@@ -12689,7 +13034,7 @@ async function analyzeUnderLock(options, access, paths, deps) {
|
|
|
12689
13034
|
]).then(() => true, () => false);
|
|
12690
13035
|
if (!isWritten) notices.add("unwritable", TUNE_MESSAGES.unwritable, 1);
|
|
12691
13036
|
if (options.json) deps.stdout(`${asciiJson(output)}\n`);
|
|
12692
|
-
else {
|
|
13037
|
+
else if (!options.reportOnly) {
|
|
12693
13038
|
deps.stdout(renderAnalysis(analyzed.map((a) => a.report), analyzed.map((a) => a.tune.usage), {
|
|
12694
13039
|
sessionCount: analyzed.length,
|
|
12695
13040
|
analyzerVersion: ANALYZER_VERSION,
|
|
@@ -12730,8 +13075,7 @@ function analyzeCommand(options, deps) {
|
|
|
12730
13075
|
}
|
|
12731
13076
|
}, deps.stderr);
|
|
12732
13077
|
}
|
|
12733
|
-
|
|
12734
|
-
//#region src/tune/purge.ts
|
|
13078
|
+
// src/tune/purge.ts
|
|
12735
13079
|
async function purgeTuneFiles(paths) {
|
|
12736
13080
|
const targets = [
|
|
12737
13081
|
paths.repositories,
|
|
@@ -12746,8 +13090,7 @@ async function purgeTuneFiles(paths) {
|
|
|
12746
13090
|
force: true
|
|
12747
13091
|
})))).every((r) => r.status === "fulfilled");
|
|
12748
13092
|
}
|
|
12749
|
-
|
|
12750
|
-
//#region src/tune/analysis-file.ts
|
|
13093
|
+
// src/tune/analysis-file.ts
|
|
12751
13094
|
async function readAnalysisSessions(path) {
|
|
12752
13095
|
const value = await readJsonFile(path);
|
|
12753
13096
|
const sessions = isObject(value) && Array.isArray(value.sessions) ? value.sessions : [];
|
|
@@ -12762,8 +13105,7 @@ async function readAnalysisSessions(path) {
|
|
|
12762
13105
|
}
|
|
12763
13106
|
return found;
|
|
12764
13107
|
}
|
|
12765
|
-
|
|
12766
|
-
//#region src/tune/record.ts
|
|
13108
|
+
// src/tune/record.ts
|
|
12767
13109
|
const MAX_RECORD_INPUT_BYTES = 1048576;
|
|
12768
13110
|
const SCOPES = [
|
|
12769
13111
|
"user",
|
|
@@ -12927,33 +13269,41 @@ async function recordProposal(text, paths, now) {
|
|
|
12927
13269
|
attributed_session_id: record.attributed_session_id
|
|
12928
13270
|
};
|
|
12929
13271
|
}
|
|
12930
|
-
|
|
12931
|
-
//#region src/tune/tune.ts
|
|
13272
|
+
// src/tune/tune.ts
|
|
12932
13273
|
const FLAGS = {
|
|
12933
13274
|
"--all": "all",
|
|
12934
13275
|
"--no-send": "noSend",
|
|
12935
13276
|
"--show-report": "showReport",
|
|
12936
|
-
"--json": "json"
|
|
13277
|
+
"--json": "json",
|
|
13278
|
+
"--report-only": "reportOnly"
|
|
12937
13279
|
};
|
|
13280
|
+
const REPORT_ONLY = "--report-only";
|
|
12938
13281
|
function parseArgs(args) {
|
|
12939
13282
|
if (args.length === 1 && args[0] === "record") return { kind: "record" };
|
|
12940
13283
|
if (args.length === 1 && args[0] === "--purge") return { kind: "purge" };
|
|
13284
|
+
if (args.length === 1 && args[0] === "--print-schedule") return { kind: "printSchedule" };
|
|
12941
13285
|
const options = {
|
|
12942
13286
|
all: false,
|
|
12943
13287
|
noSend: false,
|
|
12944
13288
|
showReport: false,
|
|
12945
|
-
json: false
|
|
13289
|
+
json: false,
|
|
13290
|
+
reportOnly: false
|
|
12946
13291
|
};
|
|
12947
13292
|
for (const arg of args) {
|
|
12948
13293
|
const key = FLAGS[arg];
|
|
12949
13294
|
if (key === void 0 || options[key]) return void 0;
|
|
12950
13295
|
options[key] = true;
|
|
12951
13296
|
}
|
|
13297
|
+
if (options.reportOnly && (options.noSend || options.showReport || options.json)) return void 0;
|
|
12952
13298
|
return {
|
|
12953
13299
|
kind: "analyze",
|
|
12954
13300
|
options
|
|
12955
13301
|
};
|
|
12956
13302
|
}
|
|
13303
|
+
function cronCommandPath(path) {
|
|
13304
|
+
return (/^[A-Za-z0-9_./-]+$/.test(path) ? path : `'${path.replaceAll("'", "'\\''")}'`).replaceAll("%", "\\%");
|
|
13305
|
+
}
|
|
13306
|
+
const scheduleLine = (selfPath) => `0 9 * * 1 ${cronCommandPath(selfPath)} tune ${REPORT_ONLY}\n`;
|
|
12957
13307
|
function canTuneSend(args) {
|
|
12958
13308
|
const parsed = parseArgs(args);
|
|
12959
13309
|
return parsed?.kind === "analyze" && !parsed.options.noSend;
|
|
@@ -13003,6 +13353,10 @@ async function tuneCommand(args, deps) {
|
|
|
13003
13353
|
return 2;
|
|
13004
13354
|
}
|
|
13005
13355
|
if (parsed.kind === "record") return record(deps);
|
|
13356
|
+
if (parsed.kind === "printSchedule") {
|
|
13357
|
+
deps.stdout(scheduleLine(deps.selfPath));
|
|
13358
|
+
return 0;
|
|
13359
|
+
}
|
|
13006
13360
|
if (parsed.kind === "purge") return underLock(deps, TUNE_MESSAGES.purgeFailed, async (paths) => {
|
|
13007
13361
|
if (!await purgeTuneFiles(paths)) return stopWith(TUNE_MESSAGES.purgeFailed);
|
|
13008
13362
|
deps.stderr(`${TUNE_MESSAGES.purged}\n`);
|
|
@@ -13010,8 +13364,7 @@ async function tuneCommand(args, deps) {
|
|
|
13010
13364
|
});
|
|
13011
13365
|
return analyzeCommand(parsed.options, deps);
|
|
13012
13366
|
}
|
|
13013
|
-
|
|
13014
|
-
//#region src/main.ts
|
|
13367
|
+
// src/main.ts
|
|
13015
13368
|
const { version } = createRequire(import.meta.url)("../package.json");
|
|
13016
13369
|
const EXIT_CLIENT_UNSUPPORTED = 5;
|
|
13017
13370
|
function parseInitArgs(args) {
|
|
@@ -13108,17 +13461,19 @@ async function run(argv, deps) {
|
|
|
13108
13461
|
git: cli.importGit,
|
|
13109
13462
|
now: () => cli.now().getTime()
|
|
13110
13463
|
}));
|
|
13464
|
+
if (command === "status") return guarded(() => statusCommand(rest, {
|
|
13465
|
+
...cli,
|
|
13466
|
+
git: cli.importGit,
|
|
13467
|
+
now: () => cli.now().getTime()
|
|
13468
|
+
}));
|
|
13111
13469
|
if (command === "tune") return guarded(() => tuneCommand(rest, {
|
|
13112
13470
|
...cli,
|
|
13113
13471
|
git: cli.importGit,
|
|
13114
13472
|
now: () => cli.now().getTime()
|
|
13115
13473
|
}));
|
|
13116
13474
|
if (command === "hook") {
|
|
13117
|
-
|
|
13118
|
-
|
|
13119
|
-
return 1;
|
|
13120
|
-
}
|
|
13121
|
-
return sessionStart({
|
|
13475
|
+
const [event] = rest;
|
|
13476
|
+
const hookDeps = {
|
|
13122
13477
|
env: cli.env,
|
|
13123
13478
|
homeDir: cli.homeDir,
|
|
13124
13479
|
now: cli.now,
|
|
@@ -13130,7 +13485,14 @@ async function run(argv, deps) {
|
|
|
13130
13485
|
stderr: cli.stderr,
|
|
13131
13486
|
client,
|
|
13132
13487
|
defaultUrl: cli.defaultUrl
|
|
13488
|
+
};
|
|
13489
|
+
if (rest.length === 1 && event === "session-start") return sessionStart(hookDeps);
|
|
13490
|
+
if (rest.length === 1 && (event === "post-tool-use" || event === "stop")) return workLocationHook(event, {
|
|
13491
|
+
...hookDeps,
|
|
13492
|
+
hookTmpDir: cli.hookTmpDir
|
|
13133
13493
|
});
|
|
13494
|
+
cli.stderr(HOOK_USAGE);
|
|
13495
|
+
return 1;
|
|
13134
13496
|
}
|
|
13135
13497
|
if (command === "top") return topCommand(rest, {
|
|
13136
13498
|
homeDir: cli.homeDir,
|
|
@@ -13156,12 +13518,12 @@ async function run(argv, deps) {
|
|
|
13156
13518
|
cli.stderr(UNKNOWN_COMMAND_HINT);
|
|
13157
13519
|
return 1;
|
|
13158
13520
|
}
|
|
13159
|
-
|
|
13160
|
-
//#region src/relaunch.ts
|
|
13521
|
+
// src/relaunch.ts
|
|
13161
13522
|
const SENDING_COMMANDS = /* @__PURE__ */ new Set([
|
|
13162
13523
|
"init",
|
|
13163
13524
|
"run",
|
|
13164
13525
|
"import",
|
|
13526
|
+
"status",
|
|
13165
13527
|
"hook"
|
|
13166
13528
|
]);
|
|
13167
13529
|
const isSending = (argv) => SENDING_COMMANDS.has(argv[0] ?? "") || argv[0] === "tune" && canTuneSend(argv.slice(1));
|
|
@@ -13185,8 +13547,7 @@ async function relaunchHf(argv, deps) {
|
|
|
13185
13547
|
deps.stderr(`${RESTART_FAILED}\n`);
|
|
13186
13548
|
return 1;
|
|
13187
13549
|
}
|
|
13188
|
-
|
|
13189
|
-
//#region src/run/command.ts
|
|
13550
|
+
// src/run/command.ts
|
|
13190
13551
|
const hasSeparator = (command, platform) => platform === "win32" ? /[\\/]/.test(command) : command.includes("/");
|
|
13191
13552
|
const contextFor = ({ platform, env, cwd, fs }) => ({
|
|
13192
13553
|
platform,
|
|
@@ -13202,8 +13563,7 @@ async function resolveAgentFile(command, options) {
|
|
|
13202
13563
|
function commandLine(file, args, options) {
|
|
13203
13564
|
return commandLineFor(file, args, contextFor(options));
|
|
13204
13565
|
}
|
|
13205
|
-
|
|
13206
|
-
//#region src/run/process.ts
|
|
13566
|
+
// src/run/process.ts
|
|
13207
13567
|
const IGNORED_WHILE_RUNNING = ["SIGINT", "SIGQUIT"];
|
|
13208
13568
|
const FORWARDED = ["SIGTERM", "SIGHUP"];
|
|
13209
13569
|
const FAILED = { kind: "failed" };
|
|
@@ -13265,8 +13625,7 @@ function spawnAndWait(command, env) {
|
|
|
13265
13625
|
}));
|
|
13266
13626
|
});
|
|
13267
13627
|
}
|
|
13268
|
-
|
|
13269
|
-
//#region src/top/process-io.ts
|
|
13628
|
+
// src/top/process-io.ts
|
|
13270
13629
|
const EXIT_SIGNALS = ["SIGTERM", "SIGHUP"];
|
|
13271
13630
|
function createProcessIo(proc = process) {
|
|
13272
13631
|
const { stdin, stdout } = proc;
|
|
@@ -13327,8 +13686,7 @@ function createProcessIo(proc = process) {
|
|
|
13327
13686
|
}
|
|
13328
13687
|
};
|
|
13329
13688
|
}
|
|
13330
|
-
|
|
13331
|
-
//#region src/bin.ts
|
|
13689
|
+
// src/bin.ts
|
|
13332
13690
|
const require = createRequire(import.meta.url);
|
|
13333
13691
|
const { url: defaultUrl, isDefault } = require("./build-config.json");
|
|
13334
13692
|
const CALLBACK_TIMEOUT_MS = 3e5;
|
|
@@ -13368,6 +13726,7 @@ process.exitCode = relaunched ?? await run(argv, {
|
|
|
13368
13726
|
devBuild: !isDefault,
|
|
13369
13727
|
callbackTimeoutMs: CALLBACK_TIMEOUT_MS,
|
|
13370
13728
|
cwd: process.cwd(),
|
|
13729
|
+
selfPath: process.argv[1],
|
|
13371
13730
|
git: createGitRunner({
|
|
13372
13731
|
platform: process.platform,
|
|
13373
13732
|
env: process.env,
|
|
@@ -13390,6 +13749,7 @@ process.exitCode = relaunched ?? await run(argv, {
|
|
|
13390
13749
|
processCwd: process.cwd(),
|
|
13391
13750
|
excludeTarget: true
|
|
13392
13751
|
}),
|
|
13752
|
+
hookTmpDir: tmpdir(),
|
|
13393
13753
|
sleep: (ms) => setTimeout$1(ms),
|
|
13394
13754
|
top: createProcessIo(),
|
|
13395
13755
|
readStdin: async (maxBytes) => {
|
|
@@ -13403,5 +13763,4 @@ process.exitCode = relaunched ?? await run(argv, {
|
|
|
13403
13763
|
return Buffer.concat(chunks);
|
|
13404
13764
|
}
|
|
13405
13765
|
});
|
|
13406
|
-
//#endregion
|
|
13407
13766
|
export {};
|