@runneth/cli 0.0.0-sha.a3c44ca880b3.production → 0.0.0-sha.c2efcbb60abb.production
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 +10 -5
- package/dist/cli.js +266 -21
- package/dist/index.d.ts +10323 -1130
- package/dist/index.js +2 -2
- package/dist/src.js +1515 -138
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ runneth send [--name default] [--timeout-ms 600000] [--stdin] -- <command...>
|
|
|
40
40
|
runneth status [--name default]
|
|
41
41
|
runneth list
|
|
42
42
|
runneth close [--name default]
|
|
43
|
-
runneth oauth login [--resource <url>] [--client-name "Runneth MCP"] [--scope <scope>] [--no-open]
|
|
43
|
+
runneth oauth login [--resource <url>] [--client-id|--clientid <id>] [--client-secret|--secret <value>] [--client-name "Runneth MCP"] [--scope <scope>] [--no-open]
|
|
44
44
|
runneth oauth status [--resource <url>]
|
|
45
45
|
runneth oauth logout [--resource <url>]
|
|
46
46
|
runneth chat --workspace <workspace-id> [--api <builder-url>] [--resource <url>] [--auth mondrian|builder] [--conversation <id>]
|
|
@@ -72,12 +72,16 @@ use a different state directory.
|
|
|
72
72
|
|
|
73
73
|
## OAuth
|
|
74
74
|
|
|
75
|
-
`runneth-cli oauth login` performs OAuth protected-resource discovery,
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
`runneth-cli oauth login` performs OAuth protected-resource discovery, PKCE
|
|
76
|
+
authorization, and token exchange through a local loopback callback. By default
|
|
77
|
+
it uses dynamic client registration as a public MCP-style client. Provide
|
|
78
|
+
`--client-id`/`--clientid` and `--client-secret`/`--secret`, or set
|
|
79
|
+
`RUNNETH_OAUTH_CLIENT_ID` and `RUNNETH_OAUTH_CLIENT_SECRET`, to use a
|
|
80
|
+
pre-registered confidential client instead.
|
|
78
81
|
|
|
79
82
|
```bash
|
|
80
83
|
runneth oauth login --resource http://localhost:4100/mcp
|
|
84
|
+
RUNNETH_OAUTH_CLIENT_ID=client-id RUNNETH_OAUTH_CLIENT_SECRET=client-secret runneth oauth login --resource http://localhost:4100/mcp
|
|
81
85
|
runneth oauth status --resource http://localhost:4100/mcp
|
|
82
86
|
```
|
|
83
87
|
|
|
@@ -118,7 +122,8 @@ runneth conversation state --workspace "$MONDRIAN_WORKSPACE_ID" --conversation "
|
|
|
118
122
|
|
|
119
123
|
`send` waits for the assistant response by default. `send` and `state` require an
|
|
120
124
|
explicit `--conversation`; the CLI does not track an implicit active
|
|
121
|
-
conversation for machine commands.
|
|
125
|
+
conversation for machine commands. State reads advertise Runneth stream V2, so
|
|
126
|
+
the same command can inspect read-only task-origin transcripts.
|
|
122
127
|
|
|
123
128
|
Long-lived agents can keep one authenticated process open with JSONL over
|
|
124
129
|
stdin/stdout:
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,142 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { resolveBuildDefaultMcpResourceUrl } from "./build-defaults.js";
|
|
3
|
-
import { B as readOAuthCredentialStatus, C as setDefaultRunnethSshTarget, D as runRunnethConversationMachineCommand,
|
|
3
|
+
import { B as readOAuthCredentialStatus, C as setDefaultRunnethSshTarget, D as runRunnethConversationMachineCommand, G as resolveSocketPath, L as loginWithOAuth, M as parseRunnethConversationArgs, N as resolveRunnethConversationOptions, P as runRunnethConversation, R as logoutOAuthCredential, S as saveRunnethSshTarget, T as runRunnethVmCopy, U as resolveOAuthToken, V as resolveOAuthClientCredentialOptions, _ as resolveRunnethSshTarget, a as normalizeSessionName, b as runOpenSsh, d as installRunnethSshAccess, f as normalizeRunnethSshTargetName, l as installRunnethSkills, m as removeRunnethSshTarget, p as readRunnethSshTargetStore, r as ensureDaemon, s as sendRunnethCliRequest, t as RunnethCliDaemon, w as assertRunnethVmCopyTargetsUseSameResource, x as runRunnethSshProxy } from "./src.js";
|
|
4
4
|
import { readFile, rm } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import process$1 from "node:process";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
9
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
10
|
+
//#region src/copy-progress.ts
|
|
11
|
+
const COPY_PROGRESS_RENDER_INTERVAL_MS = 200;
|
|
12
|
+
const COPY_PROGRESS_RENDER_INTERVAL_NON_TTY_MS = 1e4;
|
|
13
|
+
const formatErrorMessage = (error) => {
|
|
14
|
+
return error instanceof Error ? error.message : String(error);
|
|
15
|
+
};
|
|
16
|
+
const writeOutput = async (output, chunk) => {
|
|
17
|
+
await new Promise((resolve, reject) => {
|
|
18
|
+
let settled = false;
|
|
19
|
+
const rejectWrite = (error) => {
|
|
20
|
+
if (settled) return;
|
|
21
|
+
settled = true;
|
|
22
|
+
reject(error);
|
|
23
|
+
};
|
|
24
|
+
const onError = (error) => {
|
|
25
|
+
rejectWrite(error);
|
|
26
|
+
};
|
|
27
|
+
output.once("error", onError);
|
|
28
|
+
try {
|
|
29
|
+
output.write(chunk, (error) => {
|
|
30
|
+
if (error !== null && error !== void 0) {
|
|
31
|
+
rejectWrite(error);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (settled) return;
|
|
35
|
+
settled = true;
|
|
36
|
+
output.off("error", onError);
|
|
37
|
+
resolve();
|
|
38
|
+
});
|
|
39
|
+
} catch (error) {
|
|
40
|
+
output.off("error", onError);
|
|
41
|
+
rejectWrite(error);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
const formatCopyByteCount = (bytes) => {
|
|
46
|
+
const units = [
|
|
47
|
+
"B",
|
|
48
|
+
"KiB",
|
|
49
|
+
"MiB",
|
|
50
|
+
"GiB",
|
|
51
|
+
"TiB"
|
|
52
|
+
];
|
|
53
|
+
let unitIndex = 0;
|
|
54
|
+
let value = bytes;
|
|
55
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
56
|
+
value /= 1024;
|
|
57
|
+
unitIndex += 1;
|
|
58
|
+
}
|
|
59
|
+
const unit = units[unitIndex];
|
|
60
|
+
if (unit === void 0) throw new Error(`Copy byte count unit index out of range: ${String(unitIndex)}`);
|
|
61
|
+
return `${unitIndex === 0 ? String(value) : value.toFixed(value >= 100 ? 0 : 1)} ${unit}`;
|
|
62
|
+
};
|
|
63
|
+
const formatCopyDuration = (seconds) => {
|
|
64
|
+
if (!Number.isFinite(seconds) || seconds < 0) throw new Error(`Invalid copy duration: ${String(seconds)}`);
|
|
65
|
+
const totalSeconds = Math.ceil(seconds);
|
|
66
|
+
if (totalSeconds < 60) return `${String(totalSeconds)}s`;
|
|
67
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
68
|
+
const remainingSeconds = totalSeconds % 60;
|
|
69
|
+
return `${String(minutes)}m${String(remainingSeconds).padStart(2, "0")}s`;
|
|
70
|
+
};
|
|
71
|
+
const createCopyProgressReporter = (input) => {
|
|
72
|
+
let lastRenderAtMs = 0;
|
|
73
|
+
let lastRenderedLine = "";
|
|
74
|
+
let uploadStartedAtMs = null;
|
|
75
|
+
const clearLiveLine = async () => {
|
|
76
|
+
if (input.interactive && lastRenderedLine.length > 0) {
|
|
77
|
+
await writeOutput(input.output, `\r${" ".repeat(lastRenderedLine.length)}\r`);
|
|
78
|
+
lastRenderedLine = "";
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const render = async (line, force, completeLine = false) => {
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
const interval = input.interactive ? COPY_PROGRESS_RENDER_INTERVAL_MS : COPY_PROGRESS_RENDER_INTERVAL_NON_TTY_MS;
|
|
84
|
+
if (!force && now - lastRenderAtMs < interval) return;
|
|
85
|
+
lastRenderAtMs = now;
|
|
86
|
+
if (input.interactive) {
|
|
87
|
+
const padding = Math.max(0, lastRenderedLine.length - line.length);
|
|
88
|
+
await writeOutput(input.output, `\r${line}${" ".repeat(padding)}${completeLine ? "\n" : ""}`);
|
|
89
|
+
lastRenderedLine = completeLine ? "" : line;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
await writeOutput(input.output, `${line}\n`);
|
|
93
|
+
};
|
|
94
|
+
const requireUploadStartedAtMs = (label) => {
|
|
95
|
+
if (uploadStartedAtMs === null) throw new Error(`Copy progress ${label} arrived before upload-started`);
|
|
96
|
+
return uploadStartedAtMs;
|
|
97
|
+
};
|
|
98
|
+
const report = async (event) => {
|
|
99
|
+
if (event.type === "archive-preparing") {
|
|
100
|
+
await render("Runneth VM copy: preparing source archive", true);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (event.type === "archive-ready") {
|
|
104
|
+
await render(`Runneth VM copy: archived ${formatCopyByteCount(event.bytesArchived)} in ${String(event.entriesArchived)} entries (${formatCopyByteCount(event.archiveByteLength)} archive); preparing destination upload`, true);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (event.type === "upload-started") {
|
|
108
|
+
uploadStartedAtMs = Date.now();
|
|
109
|
+
await render(`Runneth VM copy: uploading 0 B / ${formatCopyByteCount(event.totalBytes)} (0%)`, true);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (event.type === "range-uploaded") {
|
|
113
|
+
const startedAtMs = requireUploadStartedAtMs(`event ${event.type}`);
|
|
114
|
+
const elapsedSeconds = Math.max(.001, (Date.now() - startedAtMs) / 1e3);
|
|
115
|
+
const bytesPerSecond = event.uploadedBytes / elapsedSeconds;
|
|
116
|
+
const percent = Math.min(100, Math.floor(event.uploadedBytes / event.totalBytes * 100));
|
|
117
|
+
const etaSeconds = (event.totalBytes - event.uploadedBytes) / bytesPerSecond;
|
|
118
|
+
await render(`Runneth VM copy: uploading ${formatCopyByteCount(event.uploadedBytes)} / ${formatCopyByteCount(event.totalBytes)} (${String(percent)}%) at ${formatCopyByteCount(bytesPerSecond)}/s, eta ${formatCopyDuration(etaSeconds)}`, event.uploadedBytes >= event.totalBytes);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (event.type === "archive-applying") {
|
|
122
|
+
await render(`Runneth VM copy: uploaded ${formatCopyByteCount(event.archiveByteLength)}; applying archive`, true, true);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
throw new Error(`Unknown copy progress event: ${JSON.stringify(event)}`);
|
|
126
|
+
};
|
|
127
|
+
return {
|
|
128
|
+
finishFailure: async (error) => {
|
|
129
|
+
try {
|
|
130
|
+
await clearLiveLine();
|
|
131
|
+
} catch (cleanupError) {
|
|
132
|
+
throw new AggregateError([error, cleanupError], `Runneth VM copy failed: ${formatErrorMessage(error)}; progress output cleanup failed: ${formatErrorMessage(cleanupError)}`);
|
|
133
|
+
}
|
|
134
|
+
throw error;
|
|
135
|
+
},
|
|
136
|
+
report
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
//#endregion
|
|
10
140
|
//#region src/ssh-stdio.ts
|
|
11
141
|
const ALLOWED_PROCESS_SIGNALS = [
|
|
12
142
|
"SIGHUP",
|
|
@@ -547,8 +677,9 @@ const runRunnethSshStdio = async (options) => {
|
|
|
547
677
|
//#region src/cli.ts
|
|
548
678
|
const DEFAULT_TIMEOUT_MS = 6e5;
|
|
549
679
|
const DEFAULT_OAUTH_CLIENT_NAME = "Runneth MCP";
|
|
550
|
-
const DEFAULT_OAUTH_SCOPE = "openid profile email offline_access";
|
|
551
680
|
const MAX_STDIN_BYTES = 1048576;
|
|
681
|
+
const RUNNETH_OAUTH_CLIENT_ID_ENV = "RUNNETH_OAUTH_CLIENT_ID";
|
|
682
|
+
const RUNNETH_OAUTH_CLIENT_SECRET_ENV = "RUNNETH_OAUTH_CLIENT_SECRET";
|
|
552
683
|
const isRecord = (value) => {
|
|
553
684
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
554
685
|
};
|
|
@@ -620,6 +751,12 @@ const parseNumber = (rawValue, label) => {
|
|
|
620
751
|
if (!Number.isInteger(value) || value <= 0) throw new Error(`${label} must be a positive integer`);
|
|
621
752
|
return value;
|
|
622
753
|
};
|
|
754
|
+
const resolveCliOAuthClientCredentialOptions = (parsed) => {
|
|
755
|
+
return resolveOAuthClientCredentialOptions({
|
|
756
|
+
clientId: parsed.clientId ?? process$1.env[RUNNETH_OAUTH_CLIENT_ID_ENV],
|
|
757
|
+
clientSecret: parsed.clientSecret ?? process$1.env[RUNNETH_OAUTH_CLIENT_SECRET_ENV]
|
|
758
|
+
});
|
|
759
|
+
};
|
|
623
760
|
const parseArgs = (argv) => {
|
|
624
761
|
const values = [];
|
|
625
762
|
let cwd;
|
|
@@ -696,10 +833,12 @@ const parseSkillsArgs = (argv) => {
|
|
|
696
833
|
};
|
|
697
834
|
const parseOAuthArgs = (argv) => {
|
|
698
835
|
const values = [];
|
|
836
|
+
let clientId;
|
|
699
837
|
let clientName = DEFAULT_OAUTH_CLIENT_NAME;
|
|
838
|
+
let clientSecret;
|
|
700
839
|
let openBrowser = true;
|
|
701
840
|
let resourceUrl;
|
|
702
|
-
let scope
|
|
841
|
+
let scope;
|
|
703
842
|
let timeoutMs = DEFAULT_TIMEOUT_MS;
|
|
704
843
|
let parseOptions = true;
|
|
705
844
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -719,9 +858,17 @@ const parseOAuthArgs = (argv) => {
|
|
|
719
858
|
return value;
|
|
720
859
|
};
|
|
721
860
|
switch (token) {
|
|
861
|
+
case "--client-id":
|
|
862
|
+
case "--clientid":
|
|
863
|
+
clientId = readValue(token);
|
|
864
|
+
break;
|
|
722
865
|
case "--client-name":
|
|
723
866
|
clientName = readValue(token);
|
|
724
867
|
break;
|
|
868
|
+
case "--client-secret":
|
|
869
|
+
case "--secret":
|
|
870
|
+
clientSecret = readValue(token);
|
|
871
|
+
break;
|
|
725
872
|
case "--no-open":
|
|
726
873
|
openBrowser = false;
|
|
727
874
|
break;
|
|
@@ -738,7 +885,9 @@ const parseOAuthArgs = (argv) => {
|
|
|
738
885
|
}
|
|
739
886
|
}
|
|
740
887
|
return {
|
|
888
|
+
...clientId === void 0 ? {} : { clientId },
|
|
741
889
|
clientName,
|
|
890
|
+
...clientSecret === void 0 ? {} : { clientSecret },
|
|
742
891
|
openBrowser,
|
|
743
892
|
resourceUrl,
|
|
744
893
|
scope,
|
|
@@ -748,12 +897,14 @@ const parseOAuthArgs = (argv) => {
|
|
|
748
897
|
};
|
|
749
898
|
const parseSshArgs = (argv) => {
|
|
750
899
|
const values = [];
|
|
900
|
+
let clientId;
|
|
751
901
|
let clientName = DEFAULT_OAUTH_CLIENT_NAME;
|
|
902
|
+
let clientSecret;
|
|
752
903
|
let identityFilePath;
|
|
753
904
|
let makeDefault = false;
|
|
754
905
|
let openBrowser = true;
|
|
755
906
|
let resourceUrl;
|
|
756
|
-
let scope
|
|
907
|
+
let scope;
|
|
757
908
|
let sshHost;
|
|
758
909
|
let sshUrl;
|
|
759
910
|
let targetName;
|
|
@@ -779,9 +930,17 @@ const parseSshArgs = (argv) => {
|
|
|
779
930
|
return value;
|
|
780
931
|
};
|
|
781
932
|
switch (token) {
|
|
933
|
+
case "--client-id":
|
|
934
|
+
case "--clientid":
|
|
935
|
+
clientId = readValue(token);
|
|
936
|
+
break;
|
|
782
937
|
case "--client-name":
|
|
783
938
|
clientName = readValue(token);
|
|
784
939
|
break;
|
|
940
|
+
case "--client-secret":
|
|
941
|
+
case "--secret":
|
|
942
|
+
clientSecret = readValue(token);
|
|
943
|
+
break;
|
|
785
944
|
case "--default":
|
|
786
945
|
makeDefault = true;
|
|
787
946
|
break;
|
|
@@ -817,7 +976,9 @@ const parseSshArgs = (argv) => {
|
|
|
817
976
|
}
|
|
818
977
|
return {
|
|
819
978
|
argumentSeparatorSeen,
|
|
979
|
+
...clientId === void 0 ? {} : { clientId },
|
|
820
980
|
clientName,
|
|
981
|
+
...clientSecret === void 0 ? {} : { clientSecret },
|
|
821
982
|
...identityFilePath === void 0 ? {} : { identityFilePath },
|
|
822
983
|
makeDefault,
|
|
823
984
|
openBrowser,
|
|
@@ -833,11 +994,13 @@ const parseSshArgs = (argv) => {
|
|
|
833
994
|
};
|
|
834
995
|
const parseCopyArgs = (argv) => {
|
|
835
996
|
const values = [];
|
|
997
|
+
let clientId;
|
|
836
998
|
let clientName = DEFAULT_OAUTH_CLIENT_NAME;
|
|
999
|
+
let clientSecret;
|
|
837
1000
|
let destinationTargetName;
|
|
838
1001
|
const ignoredPaths = [];
|
|
839
1002
|
let openBrowser = true;
|
|
840
|
-
let scope
|
|
1003
|
+
let scope;
|
|
841
1004
|
let sourceTargetName;
|
|
842
1005
|
let timeoutMs = DEFAULT_TIMEOUT_MS;
|
|
843
1006
|
let parseOptions = true;
|
|
@@ -858,9 +1021,17 @@ const parseCopyArgs = (argv) => {
|
|
|
858
1021
|
return value;
|
|
859
1022
|
};
|
|
860
1023
|
switch (token) {
|
|
1024
|
+
case "--client-id":
|
|
1025
|
+
case "--clientid":
|
|
1026
|
+
clientId = readValue(token);
|
|
1027
|
+
break;
|
|
861
1028
|
case "--client-name":
|
|
862
1029
|
clientName = readValue(token);
|
|
863
1030
|
break;
|
|
1031
|
+
case "--client-secret":
|
|
1032
|
+
case "--secret":
|
|
1033
|
+
clientSecret = readValue(token);
|
|
1034
|
+
break;
|
|
864
1035
|
case "--from":
|
|
865
1036
|
sourceTargetName = normalizeRunnethSshTargetName(readValue(token));
|
|
866
1037
|
break;
|
|
@@ -885,7 +1056,9 @@ const parseCopyArgs = (argv) => {
|
|
|
885
1056
|
if (sourceTargetName === void 0) throw new Error("runneth-cli copy requires --from <target>");
|
|
886
1057
|
if (destinationTargetName === void 0) throw new Error("runneth-cli copy requires --to <target>");
|
|
887
1058
|
return {
|
|
1059
|
+
...clientId === void 0 ? {} : { clientId },
|
|
888
1060
|
clientName,
|
|
1061
|
+
...clientSecret === void 0 ? {} : { clientSecret },
|
|
889
1062
|
destinationTargetName,
|
|
890
1063
|
ignoredPaths,
|
|
891
1064
|
openBrowser,
|
|
@@ -1001,12 +1174,12 @@ const resolveSshTargetImportEntries = (input) => {
|
|
|
1001
1174
|
});
|
|
1002
1175
|
};
|
|
1003
1176
|
const resolveSshOAuthToken = async (parsed, resourceUrl) => {
|
|
1004
|
-
|
|
1005
|
-
return await loginWithOAuth({
|
|
1177
|
+
return await resolveOAuthToken({
|
|
1006
1178
|
authorizationUrlHandler: (authorizationUrl) => {
|
|
1007
1179
|
process$1.stderr.write(`Authorize runneth-cli here:\n${authorizationUrl}\n`);
|
|
1008
1180
|
},
|
|
1009
1181
|
clientName: parsed.clientName,
|
|
1182
|
+
...resolveCliOAuthClientCredentialOptions(parsed),
|
|
1010
1183
|
openBrowser: parsed.openBrowser,
|
|
1011
1184
|
resourceUrl,
|
|
1012
1185
|
scope: parsed.scope,
|
|
@@ -1016,6 +1189,45 @@ const resolveSshOAuthToken = async (parsed, resourceUrl) => {
|
|
|
1016
1189
|
const writeJson = (response) => {
|
|
1017
1190
|
process$1.stdout.write(`${JSON.stringify(response, null, 2)}\n`);
|
|
1018
1191
|
};
|
|
1192
|
+
const formatNullableIdentityValue = (value) => {
|
|
1193
|
+
return value === null ? "(none)" : value;
|
|
1194
|
+
};
|
|
1195
|
+
const formatSshSetupAccount = (setup) => {
|
|
1196
|
+
if (setup.apiVersion === 1) return "(unavailable from API version 1)";
|
|
1197
|
+
return `${setup.provider}:${setup.providerAccountId}`;
|
|
1198
|
+
};
|
|
1199
|
+
const writeSshSetupSummary = (setup) => {
|
|
1200
|
+
const identityLines = setup.apiVersion === 1 ? [` account: ${formatSshSetupAccount(setup)}`, " targetIdentity: unavailable from API version 1"] : [
|
|
1201
|
+
` account: ${formatSshSetupAccount(setup)}`,
|
|
1202
|
+
` vmUser: ${setup.userId}`,
|
|
1203
|
+
` organization: ${setup.organizationId}`,
|
|
1204
|
+
` sandbox: ${setup.sandboxId}`,
|
|
1205
|
+
` workspace: ${formatNullableIdentityValue(setup.workspaceId)}`,
|
|
1206
|
+
` defaultConversation: ${formatNullableIdentityValue(setup.defaultConversationId)}`
|
|
1207
|
+
];
|
|
1208
|
+
process$1.stderr.write([
|
|
1209
|
+
`SSH target ${setup.targetName ?? setup.hostAlias} configured`,
|
|
1210
|
+
` config: ${setup.configPath}`,
|
|
1211
|
+
` hostAlias: ${setup.hostAlias}`,
|
|
1212
|
+
` sshUser: ${setup.sshUser}`,
|
|
1213
|
+
...identityLines,
|
|
1214
|
+
""
|
|
1215
|
+
].join("\n"));
|
|
1216
|
+
};
|
|
1217
|
+
const writeCopyTargetSummary = (input) => {
|
|
1218
|
+
process$1.stderr.write([
|
|
1219
|
+
"Runneth VM copy target binding",
|
|
1220
|
+
` source: ${input.source.targetName ?? input.source.sshUrl}`,
|
|
1221
|
+
` sourceResource: ${input.source.resourceUrl}`,
|
|
1222
|
+
` sourceSsh: ${input.source.sshUrl}`,
|
|
1223
|
+
` sourcePath: ${input.sourcePath}`,
|
|
1224
|
+
` destination: ${input.destination.targetName ?? input.destination.sshUrl}`,
|
|
1225
|
+
` destinationResource: ${input.destination.resourceUrl}`,
|
|
1226
|
+
` destinationSsh: ${input.destination.sshUrl}`,
|
|
1227
|
+
` destinationPath: ${input.destinationPath}`,
|
|
1228
|
+
""
|
|
1229
|
+
].join("\n"));
|
|
1230
|
+
};
|
|
1019
1231
|
const runDaemon = async (argv) => {
|
|
1020
1232
|
const socketPath = parseArgs(argv).values[0] ?? resolveSocketPath();
|
|
1021
1233
|
await new RunnethCliDaemon().start({ socketPath });
|
|
@@ -1091,7 +1303,11 @@ const run = async (argv) => {
|
|
|
1091
1303
|
" --workspace <id> Mondrian/Builder workspace id; may also use RUNNETH_WORKSPACE_ID",
|
|
1092
1304
|
" --token <token> Bearer token override; omitted uses stored OAuth or starts login",
|
|
1093
1305
|
" --auth <mode> mondrian or builder. Defaults to mondrian",
|
|
1306
|
+
" --client-id, --clientid <id>",
|
|
1307
|
+
" OAuth client id; may use RUNNETH_OAUTH_CLIENT_ID",
|
|
1094
1308
|
" --client-name <name> OAuth dynamic client name. Defaults to Runneth MCP",
|
|
1309
|
+
" --client-secret, --secret <value>",
|
|
1310
|
+
" OAuth client secret; prefer RUNNETH_OAUTH_CLIENT_SECRET",
|
|
1095
1311
|
" --conversation <id> Resume an existing conversation",
|
|
1096
1312
|
" --no-open Print the OAuth URL instead of opening a browser",
|
|
1097
1313
|
" --scope <scope> OAuth scope. Defaults to openid profile email offline_access",
|
|
@@ -1102,6 +1318,7 @@ const run = async (argv) => {
|
|
|
1102
1318
|
"",
|
|
1103
1319
|
"Environment:",
|
|
1104
1320
|
" RUNNETH_API_URL, RUNNETH_RESOURCE_URL, RUNNETH_WORKSPACE_ID",
|
|
1321
|
+
" RUNNETH_OAUTH_CLIENT_ID, RUNNETH_OAUTH_CLIENT_SECRET",
|
|
1105
1322
|
" RUNNETH_TOKEN, MONDRIAN_TOKEN, BUILDER_AGENT_CHAT_TOKEN",
|
|
1106
1323
|
"",
|
|
1107
1324
|
"Commands inside chat:",
|
|
@@ -1128,7 +1345,11 @@ const run = async (argv) => {
|
|
|
1128
1345
|
"Usage: runneth-cli oauth <login|status|logout> [--resource <url>] [options]",
|
|
1129
1346
|
"",
|
|
1130
1347
|
"Options:",
|
|
1348
|
+
" --client-id, --clientid <id>",
|
|
1349
|
+
" OAuth client id; may use RUNNETH_OAUTH_CLIENT_ID",
|
|
1131
1350
|
" --client-name <name> Dynamic OAuth client name",
|
|
1351
|
+
" --client-secret, --secret <val>",
|
|
1352
|
+
" OAuth client secret; prefer RUNNETH_OAUTH_CLIENT_SECRET",
|
|
1132
1353
|
" --no-open Print the authorization URL without opening a browser",
|
|
1133
1354
|
" --resource <url> OAuth-protected MCP resource URL, defaults to build MONDRIAN_API_URL/mcp",
|
|
1134
1355
|
" --scope <scope> OAuth scopes",
|
|
@@ -1145,6 +1366,7 @@ const run = async (argv) => {
|
|
|
1145
1366
|
process$1.stderr.write(`Authorize runneth-cli here:\n${authorizationUrl}\n`);
|
|
1146
1367
|
},
|
|
1147
1368
|
clientName: parsed.clientName,
|
|
1369
|
+
...resolveCliOAuthClientCredentialOptions(parsed),
|
|
1148
1370
|
openBrowser: parsed.openBrowser,
|
|
1149
1371
|
resourceUrl,
|
|
1150
1372
|
scope: parsed.scope,
|
|
@@ -1191,7 +1413,11 @@ const run = async (argv) => {
|
|
|
1191
1413
|
" --from <target> Source SSH target name",
|
|
1192
1414
|
" --to <target> Destination SSH target name",
|
|
1193
1415
|
" --ignore <path> Additional relative folder/path to skip; node_modules is skipped by default",
|
|
1416
|
+
" --client-id, --clientid <id>",
|
|
1417
|
+
" OAuth client id; may use RUNNETH_OAUTH_CLIENT_ID",
|
|
1194
1418
|
" --client-name <name> Dynamic OAuth client name",
|
|
1419
|
+
" --client-secret, --secret <val>",
|
|
1420
|
+
" OAuth client secret; prefer RUNNETH_OAUTH_CLIENT_SECRET",
|
|
1195
1421
|
" --no-open Print the authorization URL without opening a browser",
|
|
1196
1422
|
" --scope <scope> OAuth scopes",
|
|
1197
1423
|
" --timeout-ms <ms> Copy command timeout",
|
|
@@ -1212,20 +1438,35 @@ const run = async (argv) => {
|
|
|
1212
1438
|
destination: destinationTarget,
|
|
1213
1439
|
source: sourceTarget
|
|
1214
1440
|
});
|
|
1441
|
+
writeCopyTargetSummary({
|
|
1442
|
+
destination: destinationTarget,
|
|
1443
|
+
destinationPath,
|
|
1444
|
+
source: sourceTarget,
|
|
1445
|
+
sourcePath
|
|
1446
|
+
});
|
|
1215
1447
|
const oauthToken = await resolveSshOAuthToken(parsed, sourceTarget.resourceUrl);
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
...await runRunnethVmCopy({
|
|
1220
|
-
destination: destinationTarget,
|
|
1221
|
-
destinationPath,
|
|
1222
|
-
ignoredPaths: parsed.ignoredPaths,
|
|
1223
|
-
oauthToken,
|
|
1224
|
-
source: sourceTarget,
|
|
1225
|
-
sourcePath,
|
|
1226
|
-
timeoutMs: parsed.timeoutMs
|
|
1227
|
-
})
|
|
1448
|
+
const copyProgressReporter = createCopyProgressReporter({
|
|
1449
|
+
interactive: process$1.stderr.isTTY === true,
|
|
1450
|
+
output: process$1.stderr
|
|
1228
1451
|
});
|
|
1452
|
+
try {
|
|
1453
|
+
writeJson({
|
|
1454
|
+
ok: true,
|
|
1455
|
+
op: "copy",
|
|
1456
|
+
...await runRunnethVmCopy({
|
|
1457
|
+
destination: destinationTarget,
|
|
1458
|
+
destinationPath,
|
|
1459
|
+
ignoredPaths: parsed.ignoredPaths,
|
|
1460
|
+
oauthToken,
|
|
1461
|
+
onProgress: copyProgressReporter.report,
|
|
1462
|
+
source: sourceTarget,
|
|
1463
|
+
sourcePath,
|
|
1464
|
+
timeoutMs: parsed.timeoutMs
|
|
1465
|
+
})
|
|
1466
|
+
});
|
|
1467
|
+
} catch (error) {
|
|
1468
|
+
await copyProgressReporter.finishFailure(error);
|
|
1469
|
+
}
|
|
1229
1470
|
return;
|
|
1230
1471
|
}
|
|
1231
1472
|
if (command === "skills") {
|
|
@@ -1270,7 +1511,11 @@ const run = async (argv) => {
|
|
|
1270
1511
|
" target Manage named Runneth SSH VM targets",
|
|
1271
1512
|
"",
|
|
1272
1513
|
"Options:",
|
|
1514
|
+
" --client-id, --clientid <id>",
|
|
1515
|
+
" OAuth client id; may use RUNNETH_OAUTH_CLIENT_ID",
|
|
1273
1516
|
" --client-name <name> Dynamic OAuth client name",
|
|
1517
|
+
" --client-secret, --secret <val>",
|
|
1518
|
+
" OAuth client secret; prefer RUNNETH_OAUTH_CLIENT_SECRET",
|
|
1274
1519
|
" --no-open Print the authorization URL without opening a browser",
|
|
1275
1520
|
" --resource <url> OAuth-protected MCP resource URL, defaults to build MONDRIAN_API_URL/mcp when using --host or --ssh-url",
|
|
1276
1521
|
" --scope <scope> OAuth scopes",
|
|
@@ -1448,7 +1693,7 @@ const run = async (argv) => {
|
|
|
1448
1693
|
...resolveSshKeyInstallOptions(parsed),
|
|
1449
1694
|
...target.targetName === void 0 ? {} : { targetName: target.targetName }
|
|
1450
1695
|
});
|
|
1451
|
-
|
|
1696
|
+
writeSshSetupSummary(setup);
|
|
1452
1697
|
await runRunnethSshStdio({
|
|
1453
1698
|
defaultTimeoutMs: parsed.timeoutMs,
|
|
1454
1699
|
setup
|
|
@@ -1468,7 +1713,7 @@ const run = async (argv) => {
|
|
|
1468
1713
|
...resolveSshKeyInstallOptions(parsed),
|
|
1469
1714
|
...target.targetName === void 0 ? {} : { targetName: target.targetName }
|
|
1470
1715
|
});
|
|
1471
|
-
|
|
1716
|
+
writeSshSetupSummary(setup);
|
|
1472
1717
|
process$1.exitCode = await runOpenSsh({
|
|
1473
1718
|
extraArgs: parsed.values,
|
|
1474
1719
|
setup
|