@amistio/cli 0.1.3 → 0.1.4
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/dist/index.js +20 -451
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { createHash as createHash4, randomUUID
|
|
4
|
+
import { createHash as createHash4, randomUUID } from "node:crypto";
|
|
5
5
|
import { writeFile as writeFile8 } from "node:fs/promises";
|
|
6
|
-
import
|
|
7
|
-
import
|
|
6
|
+
import os5 from "node:os";
|
|
7
|
+
import path11 from "node:path";
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
|
|
10
10
|
// ../shared/src/schemas.ts
|
|
@@ -1204,8 +1204,8 @@ var toolSessionMutationSchema = z3.object({
|
|
|
1204
1204
|
});
|
|
1205
1205
|
function resolveApiUrl(apiUrl, urlPath) {
|
|
1206
1206
|
const base = apiUrl.endsWith("/") ? apiUrl.slice(0, -1) : apiUrl;
|
|
1207
|
-
const
|
|
1208
|
-
return new URL(`${base}${
|
|
1207
|
+
const path12 = urlPath.startsWith("/") ? urlPath : `/${urlPath}`;
|
|
1208
|
+
return new URL(`${base}${path12}`);
|
|
1209
1209
|
}
|
|
1210
1210
|
|
|
1211
1211
|
// src/orchestrator.ts
|
|
@@ -2880,440 +2880,12 @@ function truncateProcessOutput(value) {
|
|
|
2880
2880
|
return trimmed.length > 1200 ? `${trimmed.slice(0, 1200)}...` : trimmed;
|
|
2881
2881
|
}
|
|
2882
2882
|
|
|
2883
|
-
// src/runner-tui.ts
|
|
2884
|
-
import { randomUUID } from "node:crypto";
|
|
2885
|
-
import { readFile as readFile7 } from "node:fs/promises";
|
|
2886
|
-
import os5 from "node:os";
|
|
2887
|
-
import path11 from "node:path";
|
|
2888
|
-
import * as readline from "node:readline";
|
|
2889
|
-
async function runRunnerTui(options) {
|
|
2890
|
-
const input = options.input ?? process.stdin;
|
|
2891
|
-
const output = options.output ?? process.stdout;
|
|
2892
|
-
if (!input.isTTY || !output.isTTY) {
|
|
2893
|
-
output.write(`${runnerTuiNonInteractiveMessage()}
|
|
2894
|
-
`);
|
|
2895
|
-
return;
|
|
2896
|
-
}
|
|
2897
|
-
let selectedIndex = 0;
|
|
2898
|
-
let message;
|
|
2899
|
-
let state = await loadRunnerTuiState(options);
|
|
2900
|
-
const render = async () => {
|
|
2901
|
-
state = await loadRunnerTuiState(options);
|
|
2902
|
-
selectedIndex = clampSelectedIndex(state, selectedIndex);
|
|
2903
|
-
output.write(`\x1B[?25l\x1B[2J\x1B[H${renderRunnerTuiScreen(state, selectedIndex, { columns: output.columns, rows: output.rows, ...message ? { message } : {} })}`);
|
|
2904
|
-
};
|
|
2905
|
-
readline.emitKeypressEvents(input);
|
|
2906
|
-
input.setRawMode?.(true);
|
|
2907
|
-
input.resume();
|
|
2908
|
-
await render();
|
|
2909
|
-
await new Promise((resolve) => {
|
|
2910
|
-
let busy = false;
|
|
2911
|
-
const keypressHandler = (character, key) => {
|
|
2912
|
-
if (busy) return;
|
|
2913
|
-
busy = true;
|
|
2914
|
-
void handleRunnerTuiKey({ character, input, key, output, options, selectedIndex, state }).then(async (result) => {
|
|
2915
|
-
if (result.quit) {
|
|
2916
|
-
teardownRunnerTui(input, output, keypressHandler);
|
|
2917
|
-
resolve();
|
|
2918
|
-
return;
|
|
2919
|
-
}
|
|
2920
|
-
selectedIndex = result.selectedIndex;
|
|
2921
|
-
message = result.message;
|
|
2922
|
-
await render();
|
|
2923
|
-
}).catch(async (error) => {
|
|
2924
|
-
message = error instanceof Error ? error.message : String(error);
|
|
2925
|
-
await render();
|
|
2926
|
-
}).finally(() => {
|
|
2927
|
-
busy = false;
|
|
2928
|
-
});
|
|
2929
|
-
};
|
|
2930
|
-
input.on("keypress", keypressHandler);
|
|
2931
|
-
});
|
|
2932
|
-
}
|
|
2933
|
-
async function loadRunnerTuiState(options) {
|
|
2934
|
-
const resolvedRoot = path11.resolve(options.root);
|
|
2935
|
-
const metadata = await readProjectLink(resolvedRoot);
|
|
2936
|
-
const credentialStore = options.credentialStore ?? new LocalCredentialStore();
|
|
2937
|
-
if (!metadata) {
|
|
2938
|
-
return createRunnerTuiState({ root: resolvedRoot, apiUrl: options.apiUrl, remoteEnabled: options.remote, credentialPresent: false, remoteError: "Repository is not paired. Run `amistio pair` or `amistio import <code>` first." });
|
|
2939
|
-
}
|
|
2940
|
-
const token = await credentialStore.get(credentialKey(metadata.amistioAccountId, metadata.amistioProjectId, metadata.repositoryLinkId));
|
|
2941
|
-
const localRecords = await listRunnerDaemonMetadata({
|
|
2942
|
-
accountId: metadata.amistioAccountId,
|
|
2943
|
-
projectId: metadata.amistioProjectId,
|
|
2944
|
-
repositoryLinkId: metadata.repositoryLinkId,
|
|
2945
|
-
...options.runnerId ? { runnerId: options.runnerId } : {}
|
|
2946
|
-
}, options.metadataDir);
|
|
2947
|
-
if (!options.remote) {
|
|
2948
|
-
return createRunnerTuiState({ root: resolvedRoot, apiUrl: options.apiUrl, remoteEnabled: false, credentialPresent: Boolean(token), metadata, localRecords });
|
|
2949
|
-
}
|
|
2950
|
-
if (!token) {
|
|
2951
|
-
return createRunnerTuiState({ root: resolvedRoot, apiUrl: options.apiUrl, remoteEnabled: true, credentialPresent: false, metadata, localRecords, remoteError: "Remote runner state was not loaded because this checkout has no local runner credential." });
|
|
2952
|
-
}
|
|
2953
|
-
const client = new ApiClient({ apiUrl: options.apiUrl, accountId: metadata.amistioAccountId, token });
|
|
2954
|
-
try {
|
|
2955
|
-
const [{ runners }, preferences] = await Promise.all([
|
|
2956
|
-
client.listRunners(metadata.amistioProjectId),
|
|
2957
|
-
client.getRunnerPreferences(metadata.amistioProjectId).catch(() => void 0)
|
|
2958
|
-
]);
|
|
2959
|
-
const projectRunners = runners.filter((runner2) => runner2.repositoryLinkId === metadata.repositoryLinkId).filter((runner2) => !options.runnerId || runner2.runnerId === options.runnerId);
|
|
2960
|
-
const commandGroups = await Promise.all(projectRunners.map((runner2) => client.listRunnerCommands(metadata.amistioProjectId, runner2.runnerId, runner2.repositoryLinkId).then((result) => result.commands).catch(() => [])));
|
|
2961
|
-
return createRunnerTuiState({
|
|
2962
|
-
root: resolvedRoot,
|
|
2963
|
-
apiUrl: options.apiUrl,
|
|
2964
|
-
remoteEnabled: true,
|
|
2965
|
-
credentialPresent: true,
|
|
2966
|
-
metadata,
|
|
2967
|
-
localRecords,
|
|
2968
|
-
remoteRunners: projectRunners,
|
|
2969
|
-
remoteCommands: commandGroups.flat(),
|
|
2970
|
-
...preferences ? { preferences } : {}
|
|
2971
|
-
});
|
|
2972
|
-
} catch (error) {
|
|
2973
|
-
return createRunnerTuiState({ root: resolvedRoot, apiUrl: options.apiUrl, remoteEnabled: true, credentialPresent: true, metadata, localRecords, remoteError: error instanceof Error ? error.message : String(error) });
|
|
2974
|
-
}
|
|
2975
|
-
}
|
|
2976
|
-
function createRunnerTuiState(input) {
|
|
2977
|
-
const localRecords = input.localRecords ?? [];
|
|
2978
|
-
const remoteRunners = input.remoteRunners ?? [];
|
|
2979
|
-
const remoteCommands = input.remoteCommands ?? [];
|
|
2980
|
-
const runnersByKey = /* @__PURE__ */ new Map();
|
|
2981
|
-
for (const record of localRecords) {
|
|
2982
|
-
const runnerKey = runnerKeyFor(record.runnerId, record.repositoryLinkId);
|
|
2983
|
-
runnersByKey.set(runnerKey, {
|
|
2984
|
-
runnerId: record.runnerId,
|
|
2985
|
-
repositoryLinkId: record.repositoryLinkId,
|
|
2986
|
-
source: "local",
|
|
2987
|
-
local: {
|
|
2988
|
-
metadata: record,
|
|
2989
|
-
runtimeStatus: runnerDaemonRuntimeStatus(record),
|
|
2990
|
-
uptime: runnerDaemonRuntimeStatus(record) === "running" ? runnerDaemonUptime(record, input.nowMs) : "not running"
|
|
2991
|
-
}
|
|
2992
|
-
});
|
|
2993
|
-
}
|
|
2994
|
-
for (const heartbeat of remoteRunners) {
|
|
2995
|
-
const runnerKey = runnerKeyFor(heartbeat.runnerId, heartbeat.repositoryLinkId);
|
|
2996
|
-
const existing = runnersByKey.get(runnerKey);
|
|
2997
|
-
const latestCommand = latestRunnerCommand(remoteCommands, heartbeat.runnerId, heartbeat.repositoryLinkId);
|
|
2998
|
-
if (existing) {
|
|
2999
|
-
runnersByKey.set(runnerKey, { ...existing, heartbeat, ...latestCommand ? { latestCommand } : {} });
|
|
3000
|
-
} else {
|
|
3001
|
-
runnersByKey.set(runnerKey, { runnerId: heartbeat.runnerId, repositoryLinkId: heartbeat.repositoryLinkId, source: "remote", heartbeat, ...latestCommand ? { latestCommand } : {} });
|
|
3002
|
-
}
|
|
3003
|
-
}
|
|
3004
|
-
const runners = [...runnersByKey.values()].sort((first, second) => {
|
|
3005
|
-
if (first.source !== second.source) return first.source === "local" ? -1 : 1;
|
|
3006
|
-
return first.runnerId.localeCompare(second.runnerId);
|
|
3007
|
-
});
|
|
3008
|
-
return {
|
|
3009
|
-
root: path11.resolve(input.root),
|
|
3010
|
-
apiUrl: input.apiUrl,
|
|
3011
|
-
paired: Boolean(input.metadata),
|
|
3012
|
-
credentialPresent: input.credentialPresent,
|
|
3013
|
-
remoteEnabled: input.remoteEnabled,
|
|
3014
|
-
runners,
|
|
3015
|
-
localRunnerCount: localRecords.length,
|
|
3016
|
-
remoteRunnerCount: remoteRunners.length,
|
|
3017
|
-
...input.metadata ? { metadata: input.metadata } : {},
|
|
3018
|
-
...input.preferences ? { preferences: input.preferences } : {},
|
|
3019
|
-
...input.remoteError ? { remoteError: input.remoteError } : {}
|
|
3020
|
-
};
|
|
3021
|
-
}
|
|
3022
|
-
function renderRunnerTuiScreen(state, selectedIndex = 0, options = {}) {
|
|
3023
|
-
const columns = Math.max(40, options.columns ?? 100);
|
|
3024
|
-
const rows = Math.max(8, options.rows ?? 32);
|
|
3025
|
-
const selectedRunner = state.runners[clampSelectedIndex(state, selectedIndex)];
|
|
3026
|
-
const lines = [];
|
|
3027
|
-
lines.push("Amistio Runner UI");
|
|
3028
|
-
lines.push(`Project: ${state.metadata?.amistioProjectId ?? "unpaired"} Repository link: ${state.metadata?.repositoryLinkId ?? "none"}`);
|
|
3029
|
-
lines.push(`API: ${state.apiUrl}`);
|
|
3030
|
-
lines.push(`Root: ${state.root}`);
|
|
3031
|
-
lines.push(`Credential: ${state.credentialPresent ? "available" : "missing"} Remote: ${state.remoteEnabled ? state.remoteError ? "warning" : "enabled" : "local only"}`);
|
|
3032
|
-
if (state.remoteError) lines.push(`Remote: ${state.remoteError}`);
|
|
3033
|
-
lines.push(`Preferences: ${formatPreferenceSummary(state.preferences)}`);
|
|
3034
|
-
lines.push("");
|
|
3035
|
-
lines.push(`Runners (${state.localRunnerCount} local, ${state.remoteRunnerCount} remote heartbeat${state.remoteRunnerCount === 1 ? "" : "s"})`);
|
|
3036
|
-
if (!state.runners.length) {
|
|
3037
|
-
lines.push(" No runner records found. Press s to start a background runner, or run `amistio run --background`.");
|
|
3038
|
-
}
|
|
3039
|
-
state.runners.forEach((runner2, index) => {
|
|
3040
|
-
const marker = index === clampSelectedIndex(state, selectedIndex) ? ">" : " ";
|
|
3041
|
-
lines.push(`${marker} ${formatRunnerListLine(runner2)}`);
|
|
3042
|
-
});
|
|
3043
|
-
lines.push("");
|
|
3044
|
-
lines.push("Details");
|
|
3045
|
-
if (selectedRunner) {
|
|
3046
|
-
lines.push(...formatRunnerDetails(selectedRunner));
|
|
3047
|
-
} else {
|
|
3048
|
-
lines.push(" No runner selected.");
|
|
3049
|
-
}
|
|
3050
|
-
lines.push("");
|
|
3051
|
-
lines.push("Preference Editing");
|
|
3052
|
-
lines.push(" Account/project runner preferences are read-only here until a user-authenticated CLI path exists.");
|
|
3053
|
-
const availability = runnerTuiActionAvailability(state, selectedIndex);
|
|
3054
|
-
lines.push(`Actions: r refresh | s start${availability.start ? "" : " (needs credential)"} | x stop | b restart | u update CLI | d local remove | l logs | q quit`);
|
|
3055
|
-
if (options.message) {
|
|
3056
|
-
lines.push("");
|
|
3057
|
-
lines.push("Status");
|
|
3058
|
-
lines.push(...options.message.split("\n").map((line) => ` ${line}`));
|
|
3059
|
-
}
|
|
3060
|
-
return `${lines.slice(0, rows).map((line) => fitLine(line, columns)).join("\n")}
|
|
3061
|
-
`;
|
|
3062
|
-
}
|
|
3063
|
-
function runnerTuiActionAvailability(state, selectedIndex = 0) {
|
|
3064
|
-
const selectedRunner = state.runners[clampSelectedIndex(state, selectedIndex)];
|
|
3065
|
-
return {
|
|
3066
|
-
start: state.paired && state.credentialPresent,
|
|
3067
|
-
stop: Boolean(selectedRunner?.local),
|
|
3068
|
-
restart: Boolean(selectedRunner?.local),
|
|
3069
|
-
update: state.paired,
|
|
3070
|
-
localRemove: state.paired && state.credentialPresent,
|
|
3071
|
-
logs: Boolean(selectedRunner?.local?.metadata.logPath)
|
|
3072
|
-
};
|
|
3073
|
-
}
|
|
3074
|
-
function runnerTuiNonInteractiveMessage() {
|
|
3075
|
-
return "Terminal UI requires an interactive TTY. Use `amistio runner status` for status or `amistio run --background` to start a background runner.";
|
|
3076
|
-
}
|
|
3077
|
-
async function handleRunnerTuiKey({ character, input, key, options, output, selectedIndex, state }) {
|
|
3078
|
-
if (key.ctrl && key.name === "c" || character === "q") return { quit: true, selectedIndex };
|
|
3079
|
-
if (key.name === "up") return { selectedIndex: Math.max(0, selectedIndex - 1) };
|
|
3080
|
-
if (key.name === "down") return { selectedIndex: Math.min(Math.max(0, state.runners.length - 1), selectedIndex + 1) };
|
|
3081
|
-
if (character === "r") return { selectedIndex, message: "Refreshed runner state." };
|
|
3082
|
-
if (character === "s") return { selectedIndex, message: await promptAndStartRunner(state, options, input, output) };
|
|
3083
|
-
if (character === "x") return { selectedIndex, message: await stopSelectedRunner(state, selectedIndex, options) };
|
|
3084
|
-
if (character === "b") return { selectedIndex, message: await restartSelectedRunner(state, selectedIndex, options) };
|
|
3085
|
-
if (character === "u") return { selectedIndex, message: await confirmAndUpdateCli(input, output) };
|
|
3086
|
-
if (character === "d") return { selectedIndex, message: await confirmAndRemoveLocalCredential(state, options, input, output) };
|
|
3087
|
-
if (character === "l") return { selectedIndex, message: await readSelectedRunnerLog(state, selectedIndex) };
|
|
3088
|
-
return { selectedIndex, message: "Unknown key. Use r, s, x, b, u, d, l, or q." };
|
|
3089
|
-
}
|
|
3090
|
-
async function promptAndStartRunner(state, options, input, output) {
|
|
3091
|
-
const context = await loadRunnerTuiContext(state, options);
|
|
3092
|
-
if (!context.token) {
|
|
3093
|
-
return "Cannot start a background runner because this checkout has no local runner credential.";
|
|
3094
|
-
}
|
|
3095
|
-
const startOptions = await promptForStartRunnerOptions(input, output, options);
|
|
3096
|
-
const metadata = await startRunnerDaemon({
|
|
3097
|
-
accountId: context.metadata.amistioAccountId,
|
|
3098
|
-
projectId: context.metadata.amistioProjectId,
|
|
3099
|
-
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
3100
|
-
runnerId: startOptions.runnerId,
|
|
3101
|
-
rootDir: path11.resolve(options.root),
|
|
3102
|
-
apiUrl: options.apiUrl,
|
|
3103
|
-
args: buildBackgroundRunnerArgs({
|
|
3104
|
-
apiUrl: options.apiUrl,
|
|
3105
|
-
runnerId: startOptions.runnerId,
|
|
3106
|
-
root: options.root,
|
|
3107
|
-
...startOptions.tool ? { tool: startOptions.tool } : {},
|
|
3108
|
-
...startOptions.invocationChannel ? { invocationChannel: startOptions.invocationChannel } : {},
|
|
3109
|
-
...startOptions.model ? { model: startOptions.model } : {},
|
|
3110
|
-
session: startOptions.session,
|
|
3111
|
-
intervalSeconds: startOptions.intervalSeconds,
|
|
3112
|
-
stream: startOptions.stream
|
|
3113
|
-
}),
|
|
3114
|
-
...options.metadataDir ? { metadataDir: options.metadataDir } : {}
|
|
3115
|
-
});
|
|
3116
|
-
return `Started background runner ${metadata.runnerId} with PID ${metadata.pid}.${metadata.logPath ? `
|
|
3117
|
-
Log: ${metadata.logPath}` : ""}`;
|
|
3118
|
-
}
|
|
3119
|
-
async function promptForStartRunnerOptions(input, output, options) {
|
|
3120
|
-
const runnerId = (await promptLine(input, output, `Runner ID [${options.runnerId ?? "new"}]: `)).trim() || options.runnerId || `runner_${randomUUID()}`;
|
|
3121
|
-
const tool = parseOptionalTool(await promptLine(input, output, "AI tool / SDK [remote preference; auto, none, opencode, claude, codex, copilot, gemini, aider, cursor-agent]: "));
|
|
3122
|
-
const invocationChannel = parseOptionalInvocationChannel(await promptLine(input, output, "Invocation channel [remote preference; auto, sdk, command]: "));
|
|
3123
|
-
const model = optionalTrim(await promptLine(input, output, "Model [provider default]: "));
|
|
3124
|
-
const sessionInput = optionalTrim(await promptLine(input, output, "Session policy [auto]: ")) ?? "auto";
|
|
3125
|
-
const session = sessionPolicySchema.parse(sessionInput);
|
|
3126
|
-
const intervalInput = optionalTrim(await promptLine(input, output, `Polling interval seconds [${options.intervalSeconds}]: `));
|
|
3127
|
-
const intervalSeconds = intervalInput ? parsePositiveInteger(intervalInput) : options.intervalSeconds;
|
|
3128
|
-
const streamInput = optionalTrim(await promptLine(input, output, "Stream output from background runner? [Y/n]: "));
|
|
3129
|
-
const stream = !streamInput || streamInput.toLowerCase() === "y" || streamInput.toLowerCase() === "yes";
|
|
3130
|
-
return { runnerId, ...tool ? { tool } : {}, ...invocationChannel ? { invocationChannel } : {}, ...model ? { model } : {}, session, intervalSeconds, stream };
|
|
3131
|
-
}
|
|
3132
|
-
async function stopSelectedRunner(state, selectedIndex, options) {
|
|
3133
|
-
const selectedRunner = state.runners[clampSelectedIndex(state, selectedIndex)];
|
|
3134
|
-
if (!selectedRunner?.local) return "Select a local background runner to stop.";
|
|
3135
|
-
const stopResult = await stopRunnerDaemonProcess(selectedRunner.local.metadata);
|
|
3136
|
-
await markRunnerDaemonStopped(selectedRunner.local.metadata, options.metadataDir);
|
|
3137
|
-
const context = await loadRunnerTuiContext(state, options).catch(() => void 0);
|
|
3138
|
-
if (context?.token) {
|
|
3139
|
-
await context.client.sendRunnerHeartbeat(context.metadata.amistioProjectId, selectedRunner.runnerId, selectedRunner.repositoryLinkId, "offline", { version: "0.1.3", mode: "background", hostname: os5.hostname() }).catch(() => void 0);
|
|
3140
|
-
}
|
|
3141
|
-
return stopResult === "stopped" ? `Stopped background runner ${selectedRunner.runnerId}.` : `Marked background runner ${selectedRunner.runnerId} stopped; process was not running.`;
|
|
3142
|
-
}
|
|
3143
|
-
async function restartSelectedRunner(state, selectedIndex, options) {
|
|
3144
|
-
const selectedRunner = state.runners[clampSelectedIndex(state, selectedIndex)];
|
|
3145
|
-
if (!selectedRunner?.local) return "Select a local background runner to restart.";
|
|
3146
|
-
await stopRunnerDaemonProcess(selectedRunner.local.metadata).catch(() => "not-running");
|
|
3147
|
-
await markRunnerDaemonStopped(selectedRunner.local.metadata, options.metadataDir);
|
|
3148
|
-
const replacement = await restartRunnerDaemonProcess(
|
|
3149
|
-
selectedRunner.local.metadata,
|
|
3150
|
-
buildBackgroundRunnerArgs({ apiUrl: selectedRunner.local.metadata.apiUrl, runnerId: selectedRunner.runnerId, root: selectedRunner.local.metadata.rootDir, session: "auto", intervalSeconds: options.intervalSeconds, stream: true }),
|
|
3151
|
-
{ ...options.metadataDir ? { metadataDir: options.metadataDir } : {} }
|
|
3152
|
-
);
|
|
3153
|
-
return `Restarted background runner ${replacement.runnerId} with PID ${replacement.pid}.`;
|
|
3154
|
-
}
|
|
3155
|
-
async function confirmAndUpdateCli(input, output) {
|
|
3156
|
-
const confirmation = await promptLine(input, output, "Run the official Amistio CLI update now? Type update to continue: ");
|
|
3157
|
-
if (confirmation.trim() !== "update") return "Update cancelled.";
|
|
3158
|
-
const result = await runOfficialCliUpdate();
|
|
3159
|
-
return result.succeeded ? result.message : `${result.message}${result.error ? `
|
|
3160
|
-
${result.error}` : ""}`;
|
|
3161
|
-
}
|
|
3162
|
-
async function confirmAndRemoveLocalCredential(state, options, input, output) {
|
|
3163
|
-
const context = await loadRunnerTuiContext(state, options);
|
|
3164
|
-
if (!context.token) return "No local runner credential is stored for this paired repository.";
|
|
3165
|
-
const confirmation = await promptLine(input, output, "Remove this machine's runner credential? This does not delete source files, local checkouts, hosted repositories, project records, or team data. Type remove local to continue: ");
|
|
3166
|
-
if (confirmation.trim() !== "remove local") return "Local remove cancelled.";
|
|
3167
|
-
const localRunners = state.runners.filter((runner2) => runner2.local);
|
|
3168
|
-
for (const runner2 of localRunners) {
|
|
3169
|
-
await stopRunnerDaemonProcess(runner2.local.metadata).catch(() => "not-running");
|
|
3170
|
-
await markRunnerDaemonStopped(runner2.local.metadata, options.metadataDir).catch(() => void 0);
|
|
3171
|
-
await context.client.sendRunnerHeartbeat(context.metadata.amistioProjectId, runner2.runnerId, runner2.repositoryLinkId, "offline", { version: "0.1.3", mode: "background", hostname: os5.hostname() }).catch(() => void 0);
|
|
3172
|
-
}
|
|
3173
|
-
await context.credentialStore.delete(credentialKey(context.metadata.amistioAccountId, context.metadata.amistioProjectId, context.metadata.repositoryLinkId));
|
|
3174
|
-
return `Removed this machine's local runner credential for ${context.metadata.repositoryLinkId}. Source files, hosted repositories, project records, and team data were not deleted.`;
|
|
3175
|
-
}
|
|
3176
|
-
async function readSelectedRunnerLog(state, selectedIndex) {
|
|
3177
|
-
const selectedRunner = state.runners[clampSelectedIndex(state, selectedIndex)];
|
|
3178
|
-
const logPath = selectedRunner?.local?.metadata.logPath;
|
|
3179
|
-
if (!logPath) return "Selected runner has no local log path.";
|
|
3180
|
-
try {
|
|
3181
|
-
const content = await readFile7(logPath, "utf8");
|
|
3182
|
-
const excerpt = content.trim().slice(-3e3);
|
|
3183
|
-
return excerpt ? `Log: ${logPath}
|
|
3184
|
-
${excerpt}` : `Log: ${logPath}
|
|
3185
|
-
No log output yet.`;
|
|
3186
|
-
} catch (error) {
|
|
3187
|
-
return `Could not read ${logPath}: ${error instanceof Error ? error.message : String(error)}`;
|
|
3188
|
-
}
|
|
3189
|
-
}
|
|
3190
|
-
async function loadRunnerTuiContext(state, options) {
|
|
3191
|
-
if (!state.metadata) {
|
|
3192
|
-
throw new Error("Repository is not paired. Run `amistio pair` or `amistio import <code>` first.");
|
|
3193
|
-
}
|
|
3194
|
-
const credentialStore = options.credentialStore ?? new LocalCredentialStore();
|
|
3195
|
-
const token = await credentialStore.get(credentialKey(state.metadata.amistioAccountId, state.metadata.amistioProjectId, state.metadata.repositoryLinkId));
|
|
3196
|
-
return {
|
|
3197
|
-
metadata: state.metadata,
|
|
3198
|
-
credentialStore,
|
|
3199
|
-
...token ? { token } : {},
|
|
3200
|
-
client: new ApiClient({ apiUrl: options.apiUrl, accountId: state.metadata.amistioAccountId, ...token ? { token } : {} })
|
|
3201
|
-
};
|
|
3202
|
-
}
|
|
3203
|
-
function formatRunnerListLine(runner2) {
|
|
3204
|
-
const local = runner2.local ? `local ${runner2.local.metadata.status}/${runner2.local.runtimeStatus} pid ${runner2.local.metadata.pid}` : "remote-only";
|
|
3205
|
-
const heartbeat = runner2.heartbeat ? `heartbeat ${runner2.heartbeat.status}${runner2.heartbeat.mode ? ` ${runner2.heartbeat.mode}` : ""} ${runner2.heartbeat.lastSeenAt}` : "no heartbeat";
|
|
3206
|
-
return `${runner2.runnerId} | ${local} | ${heartbeat}`;
|
|
3207
|
-
}
|
|
3208
|
-
function formatRunnerDetails(runner2) {
|
|
3209
|
-
const lines = [];
|
|
3210
|
-
lines.push(` Runner ID: ${runner2.runnerId}`);
|
|
3211
|
-
lines.push(` Repository link: ${runner2.repositoryLinkId}`);
|
|
3212
|
-
if (runner2.local) {
|
|
3213
|
-
lines.push(` Local process: ${runner2.local.runtimeStatus}, PID ${runner2.local.metadata.pid}, uptime ${runner2.local.uptime}`);
|
|
3214
|
-
lines.push(` Local root: ${runner2.local.metadata.rootDir}`);
|
|
3215
|
-
lines.push(` Host: ${runner2.local.metadata.hostname}`);
|
|
3216
|
-
if (runner2.local.metadata.logPath) lines.push(` Log: ${runner2.local.metadata.logPath}`);
|
|
3217
|
-
} else {
|
|
3218
|
-
lines.push(" Local process: not on this machine");
|
|
3219
|
-
}
|
|
3220
|
-
if (runner2.heartbeat) {
|
|
3221
|
-
const effectiveTool = runner2.heartbeat.effectiveTool ?? runner2.heartbeat.requestedTool ?? "unknown";
|
|
3222
|
-
const requestedTool = runner2.heartbeat.requestedTool ?? "auto";
|
|
3223
|
-
const channel = runner2.heartbeat.effectiveInvocationChannel ?? runner2.heartbeat.requestedInvocationChannel ?? "auto";
|
|
3224
|
-
lines.push(` Last heartbeat: ${runner2.heartbeat.status} at ${runner2.heartbeat.lastSeenAt}${runner2.heartbeat.version ? ` (${runner2.heartbeat.version})` : ""}`);
|
|
3225
|
-
lines.push(` Tool: ${requestedTool}${effectiveTool !== requestedTool ? ` -> ${effectiveTool}` : ""}`);
|
|
3226
|
-
lines.push(` Channel: ${channel}`);
|
|
3227
|
-
lines.push(` Model: ${runner2.heartbeat.effectiveModel ?? "provider default"}`);
|
|
3228
|
-
lines.push(` Preference: ${runner2.heartbeat.preferenceSource ?? "default"} / ${runner2.heartbeat.preferenceStatus ?? "pending"}`);
|
|
3229
|
-
if (runner2.heartbeat.preferenceMessage) lines.push(` Warning: ${runner2.heartbeat.preferenceMessage}`);
|
|
3230
|
-
lines.push(` Capabilities: ${formatCapabilities(runner2.heartbeat)}`);
|
|
3231
|
-
} else {
|
|
3232
|
-
lines.push(" Last heartbeat: not loaded");
|
|
3233
|
-
}
|
|
3234
|
-
if (runner2.latestCommand) {
|
|
3235
|
-
lines.push(` Latest command: ${runner2.latestCommand.commandKind} ${runner2.latestCommand.status}${runner2.latestCommand.message ? ` - ${runner2.latestCommand.message}` : ""}`);
|
|
3236
|
-
} else {
|
|
3237
|
-
lines.push(" Latest command: none loaded");
|
|
3238
|
-
}
|
|
3239
|
-
return lines;
|
|
3240
|
-
}
|
|
3241
|
-
function formatCapabilities(runner2) {
|
|
3242
|
-
const availableCapabilities = runner2.capabilities?.filter((capability) => capability.available) ?? [];
|
|
3243
|
-
if (!availableCapabilities.length) return "unknown";
|
|
3244
|
-
return availableCapabilities.map((capability) => `${capability.name} (${capability.sdkAvailable && capability.commandAvailable ? "sdk+command" : capability.sdkAvailable ? "sdk" : capability.commandAvailable ? "command" : capability.execution})`).join(", ");
|
|
3245
|
-
}
|
|
3246
|
-
function formatPreferenceSummary(preferences) {
|
|
3247
|
-
if (!preferences) return "not loaded";
|
|
3248
|
-
const effective = preferences.effective;
|
|
3249
|
-
return `${effective.source}: ${effective.tool} / ${effective.invocationChannel} / ${effective.model ?? "provider default"}${formatSettingsSuffix(preferences.project, "project")}${formatSettingsSuffix(preferences.account, "account")}`;
|
|
3250
|
-
}
|
|
3251
|
-
function formatSettingsSuffix(settings, label) {
|
|
3252
|
-
if (!settings) return "";
|
|
3253
|
-
const preference = settings.preferences;
|
|
3254
|
-
return `; ${label} ${preference.tool ?? "unset"}/${preference.invocationChannel ?? "unset"}/${preference.model ?? "provider default"}`;
|
|
3255
|
-
}
|
|
3256
|
-
function latestRunnerCommand(commands, runnerId, repositoryLinkId) {
|
|
3257
|
-
return commands.filter((command) => command.runnerId === runnerId && command.repositoryLinkId === repositoryLinkId).sort((first, second) => Date.parse(second.createdAt) - Date.parse(first.createdAt))[0];
|
|
3258
|
-
}
|
|
3259
|
-
function runnerKeyFor(runnerId, repositoryLinkId) {
|
|
3260
|
-
return `${repositoryLinkId}:${runnerId}`;
|
|
3261
|
-
}
|
|
3262
|
-
function clampSelectedIndex(state, selectedIndex) {
|
|
3263
|
-
if (!state.runners.length) return 0;
|
|
3264
|
-
return Math.max(0, Math.min(state.runners.length - 1, selectedIndex));
|
|
3265
|
-
}
|
|
3266
|
-
function fitLine(line, columns) {
|
|
3267
|
-
if (line.length <= columns) return line;
|
|
3268
|
-
return `${line.slice(0, Math.max(0, columns - 3))}...`;
|
|
3269
|
-
}
|
|
3270
|
-
function parseOptionalTool(value) {
|
|
3271
|
-
const trimmed = optionalTrim(value);
|
|
3272
|
-
if (!trimmed) return void 0;
|
|
3273
|
-
if (trimmed === "auto" || trimmed === "none" || runnerToolNames.includes(trimmed)) {
|
|
3274
|
-
return trimmed;
|
|
3275
|
-
}
|
|
3276
|
-
throw new Error(`Expected auto, none, ${runnerToolNames.join(", ")}; received ${trimmed}.`);
|
|
3277
|
-
}
|
|
3278
|
-
function parseOptionalInvocationChannel(value) {
|
|
3279
|
-
const trimmed = optionalTrim(value);
|
|
3280
|
-
if (!trimmed) return void 0;
|
|
3281
|
-
return runnerInvocationChannelSchema.parse(trimmed);
|
|
3282
|
-
}
|
|
3283
|
-
function parsePositiveInteger(value) {
|
|
3284
|
-
const parsed = Number(value);
|
|
3285
|
-
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
3286
|
-
throw new Error(`Expected a positive integer, received ${value}.`);
|
|
3287
|
-
}
|
|
3288
|
-
return parsed;
|
|
3289
|
-
}
|
|
3290
|
-
function optionalTrim(value) {
|
|
3291
|
-
const trimmed = value.trim();
|
|
3292
|
-
return trimmed ? trimmed : void 0;
|
|
3293
|
-
}
|
|
3294
|
-
function promptLine(input, output, question) {
|
|
3295
|
-
input.setRawMode?.(false);
|
|
3296
|
-
return new Promise((resolve) => {
|
|
3297
|
-
const prompt = readline.createInterface({ input, output });
|
|
3298
|
-
prompt.question(question, (answer) => {
|
|
3299
|
-
prompt.close();
|
|
3300
|
-
input.setRawMode?.(true);
|
|
3301
|
-
resolve(answer);
|
|
3302
|
-
});
|
|
3303
|
-
});
|
|
3304
|
-
}
|
|
3305
|
-
function teardownRunnerTui(input, output, keypressHandler) {
|
|
3306
|
-
input.setRawMode?.(false);
|
|
3307
|
-
input.off("keypress", keypressHandler);
|
|
3308
|
-
output.write("\x1B[?25h\n");
|
|
3309
|
-
}
|
|
3310
|
-
|
|
3311
2883
|
// src/index.ts
|
|
3312
2884
|
var program = new Command();
|
|
3313
2885
|
var defaultRoot = process.env.INIT_CWD ?? process.cwd();
|
|
3314
2886
|
var apiUrlOptionDescription = `Amistio API URL override (or ${AMISTIO_API_URL_ENV})`;
|
|
3315
|
-
program.name("amistio").description("Amistio project brain CLI").version("0.1.
|
|
3316
|
-
var CLI_VERSION = "0.1.
|
|
2887
|
+
program.name("amistio").description("Amistio project brain CLI").version("0.1.4");
|
|
2888
|
+
var CLI_VERSION = "0.1.4";
|
|
3317
2889
|
program.command("init").description("Create Amistio control-plane folders for a new project").option("--root <path>", "Repository root", defaultRoot).action(async (options) => {
|
|
3318
2890
|
const created = await initControlPlane(options.root);
|
|
3319
2891
|
console.log(created.length ? `Created ${created.length} control-plane folders.` : "Control-plane folders already exist.");
|
|
@@ -3354,7 +2926,7 @@ program.command("bootstrap").description("Clone a linked repository locally, pre
|
|
|
3354
2926
|
console.log(`Wrote non-secret project metadata to ${filePath}.`);
|
|
3355
2927
|
console.log(`Next: cd ${formatShellArg(checkout.targetDir)} && amistio run${formatApiUrlFlag(options.apiUrl)} --watch`);
|
|
3356
2928
|
});
|
|
3357
|
-
program.command("import").description("Pair an existing checkout and import legacy Markdown docs for review").argument("[code]", "Short-lived pairing code from the Amistio app").option("--pairing-code <code>", "Short-lived pairing code from the Amistio app").option("--root <path>", "Repository root", defaultRoot).option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--default-branch <branch>", "Default branch fallback", "main").option("--include <glob>", "Only import files matching a repo-relative glob", collectRepeatedOption, []).option("--exclude <glob>", "Exclude files matching a repo-relative glob", collectRepeatedOption, []).option("--max-file-kb <kb>", "Maximum Markdown file size to import",
|
|
2929
|
+
program.command("import").description("Pair an existing checkout and import legacy Markdown docs for review").argument("[code]", "Short-lived pairing code from the Amistio app").option("--pairing-code <code>", "Short-lived pairing code from the Amistio app").option("--root <path>", "Repository root", defaultRoot).option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--default-branch <branch>", "Default branch fallback", "main").option("--include <glob>", "Only import files matching a repo-relative glob", collectRepeatedOption, []).option("--exclude <glob>", "Exclude files matching a repo-relative glob", collectRepeatedOption, []).option("--max-file-kb <kb>", "Maximum Markdown file size to import", parsePositiveInteger, 256).option("--dry-run", "Inspect and print import candidates without consuming the code or uploading documents").action(async (code, options) => {
|
|
3358
2930
|
const pairingCode = (options.pairingCode ?? code)?.trim();
|
|
3359
2931
|
if (!pairingCode) {
|
|
3360
2932
|
throw new Error("Provide a pairing code as `amistio import <code>` or with `--pairing-code <code>`.");
|
|
@@ -3423,7 +2995,7 @@ program.command("import").description("Pair an existing checkout and import lega
|
|
|
3423
2995
|
console.log(`Next: amistio sync status${formatApiUrlFlag(options.apiUrl)}`);
|
|
3424
2996
|
});
|
|
3425
2997
|
program.command("pair").description("Pair this repository with an Amistio web project").requiredOption("--account <accountId>", "Amistio account ID").requiredOption("--project <projectId>", "Amistio project ID").option("--repository-link <repositoryLinkId>", "Existing repository link ID").option("--default-branch <branch>", "Default branch", "main").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--pairing-code <code>", "Short-lived pairing code from the Amistio app").option("--token <token>", "Runner/device credential to store outside the repository").option("--root <path>", "Repository root", defaultRoot).action(async (options, command) => {
|
|
3426
|
-
let repositoryLinkId = options.repositoryLink ?? `repo_${
|
|
2998
|
+
let repositoryLinkId = options.repositoryLink ?? `repo_${randomUUID()}`;
|
|
3427
2999
|
let credential = options.token;
|
|
3428
3000
|
if (options.pairingCode) {
|
|
3429
3001
|
const pairing = await new ApiClient({
|
|
@@ -3605,7 +3177,7 @@ program.command("orchestrate").description("Update the Amistio control plane thr
|
|
|
3605
3177
|
...options.toolCommand ? { toolCommand: options.toolCommand } : {},
|
|
3606
3178
|
...options.model ? { model: options.model } : {},
|
|
3607
3179
|
streamOutput: options.stream,
|
|
3608
|
-
...sessionPolicy === "none" ? {} : { session: { toolSessionId: `local_orchestration_${
|
|
3180
|
+
...sessionPolicy === "none" ? {} : { session: { toolSessionId: `local_orchestration_${randomUUID()}`, policy: sessionPolicy, decision: localSessionDecision(sessionPolicy) } }
|
|
3609
3181
|
});
|
|
3610
3182
|
if (!options.stream && result.stdout.trim()) {
|
|
3611
3183
|
console.log(result.stdout.trim());
|
|
@@ -3617,7 +3189,7 @@ program.command("orchestrate").description("Update the Amistio control plane thr
|
|
|
3617
3189
|
process.exitCode = result.exitCode;
|
|
3618
3190
|
}
|
|
3619
3191
|
});
|
|
3620
|
-
program.command("run").description("Claim and run approved Amistio work locally").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--runner-id <runnerId>", "Stable runner ID", `runner_${
|
|
3192
|
+
program.command("run").description("Claim and run approved Amistio work locally").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--runner-id <runnerId>", "Stable runner ID", `runner_${randomUUID()}`).option("--root <path>", "Repository root", defaultRoot).option("--tool <name>", "Local tool to use: auto, none, opencode, claude, codex, copilot, gemini, aider, cursor-agent").option("--invocation-channel <channel>", "Local invocation channel: auto, sdk, or command", parseInvocationChannel).option("--model <model>", "Model to request when the selected local tool supports model selection").option("--tool-command <command>", "Custom local command. Use {promptFile} and {root} placeholders when supported").option("--session <policy>", "Tool session policy: auto, new, continue:<toolSessionId>, or none", "auto").option("--dry-run", "Claim work and print the generated execution prompt without running a tool").option("--watch", "Keep polling for approved work until stopped").option("--background", "Start a detached background runner that watches for approved work").option("--interval-seconds <seconds>", "Polling interval for --watch", parsePositiveInteger, 10).option("--max-iterations <count>", "Stop watch mode after this many polling attempts", parsePositiveInteger).option("--no-stream", "Capture local tool output instead of streaming it").action(async (options, command) => {
|
|
3621
3193
|
const context = await loadPairedApiContext(options.root, options.apiUrl);
|
|
3622
3194
|
if (!context) {
|
|
3623
3195
|
console.log("Repository is not paired. Run `amistio pair` first.");
|
|
@@ -3639,7 +3211,7 @@ program.command("run").description("Claim and run approved Amistio work locally"
|
|
|
3639
3211
|
projectId: context.metadata.amistioProjectId,
|
|
3640
3212
|
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
3641
3213
|
runnerId: options.runnerId,
|
|
3642
|
-
rootDir:
|
|
3214
|
+
rootDir: path11.resolve(options.root),
|
|
3643
3215
|
apiUrl: options.apiUrl,
|
|
3644
3216
|
args: buildBackgroundRunnerArgs(options)
|
|
3645
3217
|
});
|
|
@@ -3706,9 +3278,6 @@ program.command("run").description("Claim and run approved Amistio work locally"
|
|
|
3706
3278
|
}
|
|
3707
3279
|
});
|
|
3708
3280
|
var runner = program.command("runner").description("Manage local Amistio runner processes");
|
|
3709
|
-
runner.command("ui").alias("tui").description("Open an interactive terminal UI for local runner management").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--root <path>", "Repository root", defaultRoot).option("--runner-id <runnerId>", "Select or prefill a runner ID").option("--interval-seconds <seconds>", "Default polling interval for started background runners", parsePositiveInteger2, 10).option("--no-remote", "Skip remote API calls and show local runner state only").action(async (options) => {
|
|
3710
|
-
await runRunnerTui(options);
|
|
3711
|
-
});
|
|
3712
3281
|
runner.command("status").description("Show background runner status for the paired repository").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--root <path>", "Repository root", defaultRoot).option("--runner-id <runnerId>", "Limit status to one runner ID").action(async (options) => {
|
|
3713
3282
|
const context = await loadPairedApiContext(options.root, options.apiUrl);
|
|
3714
3283
|
if (!context) {
|
|
@@ -3912,7 +3481,7 @@ async function runNextWorkItem({
|
|
|
3912
3481
|
projectId,
|
|
3913
3482
|
result.workItem.workItemId,
|
|
3914
3483
|
finalStatus,
|
|
3915
|
-
`run_${result.workItem.workItemId}_${
|
|
3484
|
+
`run_${result.workItem.workItemId}_${randomUUID()}`,
|
|
3916
3485
|
runnerId,
|
|
3917
3486
|
{
|
|
3918
3487
|
tool: preview.toolName,
|
|
@@ -3958,7 +3527,7 @@ async function updateRunnerCommandStatus(apiClient, context, command, status, me
|
|
|
3958
3527
|
runnerId: context.runnerId,
|
|
3959
3528
|
repositoryLinkId: context.repositoryLinkId,
|
|
3960
3529
|
status,
|
|
3961
|
-
idempotencyKey: `runner_command_${command.commandId}_${status}_${
|
|
3530
|
+
idempotencyKey: `runner_command_${command.commandId}_${status}_${randomUUID()}`,
|
|
3962
3531
|
message,
|
|
3963
3532
|
...error ? { error } : {}
|
|
3964
3533
|
});
|
|
@@ -4043,7 +3612,7 @@ ${toolResult.stderr}`);
|
|
|
4043
3612
|
const result = await apiClient.submitBrainGenerationResult(projectId, workItem.workItemId, {
|
|
4044
3613
|
status: "completed",
|
|
4045
3614
|
runnerId,
|
|
4046
|
-
idempotencyKey: `generation_${workItem.workItemId}_${
|
|
3615
|
+
idempotencyKey: `generation_${workItem.workItemId}_${randomUUID()}`,
|
|
4047
3616
|
artifacts,
|
|
4048
3617
|
tool: toolName,
|
|
4049
3618
|
durationMs,
|
|
@@ -4057,7 +3626,7 @@ ${toolResult.stderr}`);
|
|
|
4057
3626
|
await apiClient.submitBrainGenerationResult(projectId, workItem.workItemId, {
|
|
4058
3627
|
status: "failed",
|
|
4059
3628
|
runnerId,
|
|
4060
|
-
idempotencyKey: `generation_${workItem.workItemId}_${
|
|
3629
|
+
idempotencyKey: `generation_${workItem.workItemId}_${randomUUID()}`,
|
|
4061
3630
|
tool: toolName,
|
|
4062
3631
|
durationMs,
|
|
4063
3632
|
...sessionTelemetry,
|
|
@@ -4173,7 +3742,7 @@ async function prepareToolSession({
|
|
|
4173
3742
|
});
|
|
4174
3743
|
return { ...selection, toolSession: toolSession2 };
|
|
4175
3744
|
}
|
|
4176
|
-
const toolSessionId = `tool_session_${
|
|
3745
|
+
const toolSessionId = `tool_session_${randomUUID()}`;
|
|
4177
3746
|
const { toolSession } = await apiClient.createToolSession(projectId, {
|
|
4178
3747
|
toolSessionId,
|
|
4179
3748
|
repositoryLinkId,
|
|
@@ -4261,7 +3830,7 @@ function collectRepeatedOption(value, previous) {
|
|
|
4261
3830
|
function formatImportSkipSummary(counts) {
|
|
4262
3831
|
return `Skipped: ${counts.excluded} excluded, ${counts.tooLarge} too large, ${counts.alreadyManaged} already managed, ${counts.unreadable} unreadable, ${counts.notMarkdown} non-Markdown.`;
|
|
4263
3832
|
}
|
|
4264
|
-
function
|
|
3833
|
+
function parsePositiveInteger(value) {
|
|
4265
3834
|
const parsed = Number(value);
|
|
4266
3835
|
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
4267
3836
|
throw new Error(`Expected a positive integer, received ${value}.`);
|
|
@@ -4275,7 +3844,7 @@ function parseInvocationChannel(value) {
|
|
|
4275
3844
|
throw new Error(`Expected invocation channel auto, sdk, or command; received ${value}.`);
|
|
4276
3845
|
}
|
|
4277
3846
|
function inferRepoName(root) {
|
|
4278
|
-
return
|
|
3847
|
+
return path11.basename(path11.resolve(root)) || "repository";
|
|
4279
3848
|
}
|
|
4280
3849
|
function createRepoFingerprint(accountId, projectId, repositoryLinkId) {
|
|
4281
3850
|
return createHash4("sha256").update(`${accountId}:${projectId}:${repositoryLinkId}`).digest("hex");
|
|
@@ -4399,7 +3968,7 @@ function runnerHeartbeatMetadata(toolConfig, mode = currentRunnerMode()) {
|
|
|
4399
3968
|
return {
|
|
4400
3969
|
version: CLI_VERSION,
|
|
4401
3970
|
mode,
|
|
4402
|
-
hostname:
|
|
3971
|
+
hostname: os5.hostname(),
|
|
4403
3972
|
...toolConfig?.capabilities ? { capabilities: toolConfig.capabilities } : {},
|
|
4404
3973
|
...toolConfig?.requestedTool ? { requestedTool: toolConfig.requestedTool } : {},
|
|
4405
3974
|
...toolConfig?.requestedInvocationChannel ? { requestedInvocationChannel: toolConfig.requestedInvocationChannel } : {},
|