@amistio/cli 0.1.3 → 0.1.5
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 +31 -452
- 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
|
|
@@ -2645,7 +2645,7 @@ function buildImportedBrainDocuments(options) {
|
|
|
2645
2645
|
documentId,
|
|
2646
2646
|
documentType: candidate.documentType,
|
|
2647
2647
|
title: candidate.title,
|
|
2648
|
-
status: "
|
|
2648
|
+
status: "approved",
|
|
2649
2649
|
repoPath: candidate.repoPath,
|
|
2650
2650
|
content: candidate.content,
|
|
2651
2651
|
contentHash: candidate.contentHash,
|
|
@@ -2658,10 +2658,10 @@ function buildImportedBrainDocuments(options) {
|
|
|
2658
2658
|
},
|
|
2659
2659
|
revision,
|
|
2660
2660
|
source: "repo",
|
|
2661
|
-
syncState: "
|
|
2661
|
+
syncState: "approved",
|
|
2662
2662
|
createdAt: existing?.createdAt ?? importedAt,
|
|
2663
2663
|
updatedAt: importedAt,
|
|
2664
|
-
|
|
2664
|
+
approvedRevision: revision
|
|
2665
2665
|
};
|
|
2666
2666
|
});
|
|
2667
2667
|
}
|
|
@@ -2880,440 +2880,22 @@ function truncateProcessOutput(value) {
|
|
|
2880
2880
|
return trimmed.length > 1200 ? `${trimmed.slice(0, 1200)}...` : trimmed;
|
|
2881
2881
|
}
|
|
2882
2882
|
|
|
2883
|
-
// src/
|
|
2884
|
-
import {
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
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;
|
|
2883
|
+
// src/version.ts
|
|
2884
|
+
import { readFileSync } from "node:fs";
|
|
2885
|
+
function readCliPackageVersion() {
|
|
2886
|
+
const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
2887
|
+
if (typeof packageJson.version !== "string" || !packageJson.version.trim()) {
|
|
2888
|
+
throw new Error("@amistio/cli package version is missing.");
|
|
3275
2889
|
}
|
|
3276
|
-
|
|
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");
|
|
2890
|
+
return packageJson.version.trim();
|
|
3309
2891
|
}
|
|
2892
|
+
var CLI_VERSION = readCliPackageVersion();
|
|
3310
2893
|
|
|
3311
2894
|
// src/index.ts
|
|
3312
2895
|
var program = new Command();
|
|
3313
2896
|
var defaultRoot = process.env.INIT_CWD ?? process.cwd();
|
|
3314
2897
|
var apiUrlOptionDescription = `Amistio API URL override (or ${AMISTIO_API_URL_ENV})`;
|
|
3315
|
-
program.name("amistio").description("Amistio project brain CLI").version(
|
|
3316
|
-
var CLI_VERSION = "0.1.3";
|
|
2898
|
+
program.name("amistio").description("Amistio project brain CLI").version(CLI_VERSION);
|
|
3317
2899
|
program.command("init").description("Create Amistio control-plane folders for a new project").option("--root <path>", "Repository root", defaultRoot).action(async (options) => {
|
|
3318
2900
|
const created = await initControlPlane(options.root);
|
|
3319
2901
|
console.log(created.length ? `Created ${created.length} control-plane folders.` : "Control-plane folders already exist.");
|
|
@@ -3354,7 +2936,7 @@ program.command("bootstrap").description("Clone a linked repository locally, pre
|
|
|
3354
2936
|
console.log(`Wrote non-secret project metadata to ${filePath}.`);
|
|
3355
2937
|
console.log(`Next: cd ${formatShellArg(checkout.targetDir)} && amistio run${formatApiUrlFlag(options.apiUrl)} --watch`);
|
|
3356
2938
|
});
|
|
3357
|
-
program.command("import").description("Pair an existing checkout and import legacy Markdown docs
|
|
2939
|
+
program.command("import").description("Pair an existing checkout and import legacy Markdown docs as accepted brain records").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
2940
|
const pairingCode = (options.pairingCode ?? code)?.trim();
|
|
3359
2941
|
if (!pairingCode) {
|
|
3360
2942
|
throw new Error("Provide a pairing code as `amistio import <code>` or with `--pairing-code <code>`.");
|
|
@@ -3419,11 +3001,11 @@ program.command("import").description("Pair an existing checkout and import lega
|
|
|
3419
3001
|
}
|
|
3420
3002
|
console.log(`Pairing confirmed for ${pairing.repositoryLink.repoName}; repository link ${pairing.repositoryLinkAction}.`);
|
|
3421
3003
|
console.log(`Wrote non-secret project metadata to ${metadataFilePath}.`);
|
|
3422
|
-
console.log(`Imported ${documents.length} legacy document${documents.length === 1 ? "" : "s"}
|
|
3004
|
+
console.log(`Imported ${documents.length} legacy document${documents.length === 1 ? "" : "s"} as accepted brain record${documents.length === 1 ? "" : "s"}.`);
|
|
3423
3005
|
console.log(`Next: amistio sync status${formatApiUrlFlag(options.apiUrl)}`);
|
|
3424
3006
|
});
|
|
3425
3007
|
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_${
|
|
3008
|
+
let repositoryLinkId = options.repositoryLink ?? `repo_${randomUUID()}`;
|
|
3427
3009
|
let credential = options.token;
|
|
3428
3010
|
if (options.pairingCode) {
|
|
3429
3011
|
const pairing = await new ApiClient({
|
|
@@ -3605,7 +3187,7 @@ program.command("orchestrate").description("Update the Amistio control plane thr
|
|
|
3605
3187
|
...options.toolCommand ? { toolCommand: options.toolCommand } : {},
|
|
3606
3188
|
...options.model ? { model: options.model } : {},
|
|
3607
3189
|
streamOutput: options.stream,
|
|
3608
|
-
...sessionPolicy === "none" ? {} : { session: { toolSessionId: `local_orchestration_${
|
|
3190
|
+
...sessionPolicy === "none" ? {} : { session: { toolSessionId: `local_orchestration_${randomUUID()}`, policy: sessionPolicy, decision: localSessionDecision(sessionPolicy) } }
|
|
3609
3191
|
});
|
|
3610
3192
|
if (!options.stream && result.stdout.trim()) {
|
|
3611
3193
|
console.log(result.stdout.trim());
|
|
@@ -3617,7 +3199,7 @@ program.command("orchestrate").description("Update the Amistio control plane thr
|
|
|
3617
3199
|
process.exitCode = result.exitCode;
|
|
3618
3200
|
}
|
|
3619
3201
|
});
|
|
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_${
|
|
3202
|
+
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
3203
|
const context = await loadPairedApiContext(options.root, options.apiUrl);
|
|
3622
3204
|
if (!context) {
|
|
3623
3205
|
console.log("Repository is not paired. Run `amistio pair` first.");
|
|
@@ -3639,7 +3221,7 @@ program.command("run").description("Claim and run approved Amistio work locally"
|
|
|
3639
3221
|
projectId: context.metadata.amistioProjectId,
|
|
3640
3222
|
repositoryLinkId: context.metadata.repositoryLinkId,
|
|
3641
3223
|
runnerId: options.runnerId,
|
|
3642
|
-
rootDir:
|
|
3224
|
+
rootDir: path11.resolve(options.root),
|
|
3643
3225
|
apiUrl: options.apiUrl,
|
|
3644
3226
|
args: buildBackgroundRunnerArgs(options)
|
|
3645
3227
|
});
|
|
@@ -3706,9 +3288,6 @@ program.command("run").description("Claim and run approved Amistio work locally"
|
|
|
3706
3288
|
}
|
|
3707
3289
|
});
|
|
3708
3290
|
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
3291
|
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
3292
|
const context = await loadPairedApiContext(options.root, options.apiUrl);
|
|
3714
3293
|
if (!context) {
|
|
@@ -3912,7 +3491,7 @@ async function runNextWorkItem({
|
|
|
3912
3491
|
projectId,
|
|
3913
3492
|
result.workItem.workItemId,
|
|
3914
3493
|
finalStatus,
|
|
3915
|
-
`run_${result.workItem.workItemId}_${
|
|
3494
|
+
`run_${result.workItem.workItemId}_${randomUUID()}`,
|
|
3916
3495
|
runnerId,
|
|
3917
3496
|
{
|
|
3918
3497
|
tool: preview.toolName,
|
|
@@ -3958,7 +3537,7 @@ async function updateRunnerCommandStatus(apiClient, context, command, status, me
|
|
|
3958
3537
|
runnerId: context.runnerId,
|
|
3959
3538
|
repositoryLinkId: context.repositoryLinkId,
|
|
3960
3539
|
status,
|
|
3961
|
-
idempotencyKey: `runner_command_${command.commandId}_${status}_${
|
|
3540
|
+
idempotencyKey: `runner_command_${command.commandId}_${status}_${randomUUID()}`,
|
|
3962
3541
|
message,
|
|
3963
3542
|
...error ? { error } : {}
|
|
3964
3543
|
});
|
|
@@ -4043,7 +3622,7 @@ ${toolResult.stderr}`);
|
|
|
4043
3622
|
const result = await apiClient.submitBrainGenerationResult(projectId, workItem.workItemId, {
|
|
4044
3623
|
status: "completed",
|
|
4045
3624
|
runnerId,
|
|
4046
|
-
idempotencyKey: `generation_${workItem.workItemId}_${
|
|
3625
|
+
idempotencyKey: `generation_${workItem.workItemId}_${randomUUID()}`,
|
|
4047
3626
|
artifacts,
|
|
4048
3627
|
tool: toolName,
|
|
4049
3628
|
durationMs,
|
|
@@ -4057,7 +3636,7 @@ ${toolResult.stderr}`);
|
|
|
4057
3636
|
await apiClient.submitBrainGenerationResult(projectId, workItem.workItemId, {
|
|
4058
3637
|
status: "failed",
|
|
4059
3638
|
runnerId,
|
|
4060
|
-
idempotencyKey: `generation_${workItem.workItemId}_${
|
|
3639
|
+
idempotencyKey: `generation_${workItem.workItemId}_${randomUUID()}`,
|
|
4061
3640
|
tool: toolName,
|
|
4062
3641
|
durationMs,
|
|
4063
3642
|
...sessionTelemetry,
|
|
@@ -4173,7 +3752,7 @@ async function prepareToolSession({
|
|
|
4173
3752
|
});
|
|
4174
3753
|
return { ...selection, toolSession: toolSession2 };
|
|
4175
3754
|
}
|
|
4176
|
-
const toolSessionId = `tool_session_${
|
|
3755
|
+
const toolSessionId = `tool_session_${randomUUID()}`;
|
|
4177
3756
|
const { toolSession } = await apiClient.createToolSession(projectId, {
|
|
4178
3757
|
toolSessionId,
|
|
4179
3758
|
repositoryLinkId,
|
|
@@ -4261,7 +3840,7 @@ function collectRepeatedOption(value, previous) {
|
|
|
4261
3840
|
function formatImportSkipSummary(counts) {
|
|
4262
3841
|
return `Skipped: ${counts.excluded} excluded, ${counts.tooLarge} too large, ${counts.alreadyManaged} already managed, ${counts.unreadable} unreadable, ${counts.notMarkdown} non-Markdown.`;
|
|
4263
3842
|
}
|
|
4264
|
-
function
|
|
3843
|
+
function parsePositiveInteger(value) {
|
|
4265
3844
|
const parsed = Number(value);
|
|
4266
3845
|
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
4267
3846
|
throw new Error(`Expected a positive integer, received ${value}.`);
|
|
@@ -4275,7 +3854,7 @@ function parseInvocationChannel(value) {
|
|
|
4275
3854
|
throw new Error(`Expected invocation channel auto, sdk, or command; received ${value}.`);
|
|
4276
3855
|
}
|
|
4277
3856
|
function inferRepoName(root) {
|
|
4278
|
-
return
|
|
3857
|
+
return path11.basename(path11.resolve(root)) || "repository";
|
|
4279
3858
|
}
|
|
4280
3859
|
function createRepoFingerprint(accountId, projectId, repositoryLinkId) {
|
|
4281
3860
|
return createHash4("sha256").update(`${accountId}:${projectId}:${repositoryLinkId}`).digest("hex");
|
|
@@ -4399,7 +3978,7 @@ function runnerHeartbeatMetadata(toolConfig, mode = currentRunnerMode()) {
|
|
|
4399
3978
|
return {
|
|
4400
3979
|
version: CLI_VERSION,
|
|
4401
3980
|
mode,
|
|
4402
|
-
hostname:
|
|
3981
|
+
hostname: os5.hostname(),
|
|
4403
3982
|
...toolConfig?.capabilities ? { capabilities: toolConfig.capabilities } : {},
|
|
4404
3983
|
...toolConfig?.requestedTool ? { requestedTool: toolConfig.requestedTool } : {},
|
|
4405
3984
|
...toolConfig?.requestedInvocationChannel ? { requestedInvocationChannel: toolConfig.requestedInvocationChannel } : {},
|