@treeseed/cli 0.4.12 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/handlers/auth-login.js +58 -46
- package/dist/cli/handlers/auth-logout.js +23 -11
- package/dist/cli/handlers/auth-whoami.js +27 -16
- package/dist/cli/handlers/config-ui.d.ts +65 -9
- package/dist/cli/handlers/config-ui.js +561 -175
- package/dist/cli/handlers/config.js +177 -11
- package/dist/cli/handlers/dev.js +6 -1
- package/dist/cli/handlers/doctor.js +11 -5
- package/dist/cli/handlers/secret-prompts.d.ts +2 -0
- package/dist/cli/handlers/secret-prompts.js +54 -0
- package/dist/cli/handlers/secrets.d.ts +7 -0
- package/dist/cli/handlers/secrets.js +175 -0
- package/dist/cli/handlers/status.js +31 -5
- package/dist/cli/handlers/workflow.js +31 -0
- package/dist/cli/help-ui.js +1 -1
- package/dist/cli/operations-registry.js +129 -9
- package/dist/cli/registry.d.ts +6 -0
- package/dist/cli/registry.js +15 -1
- package/dist/cli/repair.js +5 -9
- package/dist/cli/ui/framework.d.ts +2 -0
- package/dist/cli/ui/framework.js +53 -22
- package/dist/cli/ui/mouse.d.ts +3 -1
- package/dist/cli/ui/mouse.js +3 -3
- package/package.json +7 -6
- package/scripts/verify-driver.mjs +34 -0
|
@@ -1,66 +1,78 @@
|
|
|
1
1
|
import { RemoteTreeseedAuthClient, RemoteTreeseedClient } from "@treeseed/sdk/remote";
|
|
2
2
|
import {
|
|
3
3
|
resolveTreeseedRemoteConfig,
|
|
4
|
-
setTreeseedRemoteSession
|
|
4
|
+
setTreeseedRemoteSession,
|
|
5
|
+
TreeseedKeyAgentError
|
|
5
6
|
} from "@treeseed/sdk/workflow-support";
|
|
6
7
|
import { guidedResult } from "./utils.js";
|
|
7
8
|
function sleep(ms) {
|
|
8
9
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
10
|
}
|
|
10
11
|
const handleAuthLogin = async (invocation, context) => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
context.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
accessToken: response.accessToken,
|
|
34
|
-
refreshToken: response.refreshToken,
|
|
35
|
-
expiresAt: response.expiresAt,
|
|
36
|
-
principal: response.principal
|
|
37
|
-
});
|
|
38
|
-
return guidedResult({
|
|
39
|
-
command: "auth:login",
|
|
40
|
-
summary: "Treeseed API login completed successfully.",
|
|
41
|
-
facts: [
|
|
42
|
-
{ label: "Host", value: hostId },
|
|
43
|
-
{ label: "Principal", value: response.principal.displayName ?? response.principal.id },
|
|
44
|
-
{ label: "Scopes", value: response.principal.scopes.join(", ") }
|
|
45
|
-
],
|
|
46
|
-
report: {
|
|
12
|
+
try {
|
|
13
|
+
const tenantRoot = context.cwd;
|
|
14
|
+
const remoteConfig = resolveTreeseedRemoteConfig(tenantRoot, context.env);
|
|
15
|
+
const hostId = typeof invocation.args.host === "string" ? invocation.args.host : remoteConfig.activeHostId;
|
|
16
|
+
const client = new RemoteTreeseedAuthClient(new RemoteTreeseedClient({
|
|
17
|
+
...remoteConfig,
|
|
18
|
+
activeHostId: hostId
|
|
19
|
+
}));
|
|
20
|
+
const started = await client.startDeviceFlow({
|
|
21
|
+
clientName: "treeseed-cli",
|
|
22
|
+
scopes: ["auth:me", "sdk", "operations"]
|
|
23
|
+
});
|
|
24
|
+
if (context.outputFormat !== "json") {
|
|
25
|
+
context.write(`Open ${started.verificationUriComplete}`, "stdout");
|
|
26
|
+
context.write(`User code: ${started.userCode}`, "stdout");
|
|
27
|
+
context.write("Waiting for approval...", "stdout");
|
|
28
|
+
}
|
|
29
|
+
const deadline = Date.parse(started.expiresAt);
|
|
30
|
+
while (Date.now() < deadline) {
|
|
31
|
+
const response = await client.pollDeviceFlow({ deviceCode: started.deviceCode });
|
|
32
|
+
if (response.ok && response.status === "approved") {
|
|
33
|
+
setTreeseedRemoteSession(tenantRoot, {
|
|
47
34
|
hostId,
|
|
35
|
+
accessToken: response.accessToken,
|
|
36
|
+
refreshToken: response.refreshToken,
|
|
37
|
+
expiresAt: response.expiresAt,
|
|
48
38
|
principal: response.principal
|
|
49
|
-
}
|
|
50
|
-
|
|
39
|
+
});
|
|
40
|
+
return guidedResult({
|
|
41
|
+
command: "auth:login",
|
|
42
|
+
summary: "Treeseed API login completed successfully.",
|
|
43
|
+
facts: [
|
|
44
|
+
{ label: "Host", value: hostId },
|
|
45
|
+
{ label: "Principal", value: response.principal.displayName ?? response.principal.id },
|
|
46
|
+
{ label: "Scopes", value: response.principal.scopes.join(", ") }
|
|
47
|
+
],
|
|
48
|
+
report: {
|
|
49
|
+
hostId,
|
|
50
|
+
principal: response.principal
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (!response.ok && response.status !== "already_used") {
|
|
55
|
+
return {
|
|
56
|
+
exitCode: 1,
|
|
57
|
+
stderr: [response.error]
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
await sleep(started.intervalSeconds * 1e3);
|
|
51
61
|
}
|
|
52
|
-
|
|
62
|
+
return {
|
|
63
|
+
exitCode: 1,
|
|
64
|
+
stderr: ["Treeseed API login expired before approval completed."]
|
|
65
|
+
};
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (error instanceof TreeseedKeyAgentError) {
|
|
53
68
|
return {
|
|
54
69
|
exitCode: 1,
|
|
55
|
-
stderr: [
|
|
70
|
+
stderr: [error.message],
|
|
71
|
+
report: { command: "auth:login", ok: false, code: error.code, details: error.details ?? null }
|
|
56
72
|
};
|
|
57
73
|
}
|
|
58
|
-
|
|
74
|
+
throw error;
|
|
59
75
|
}
|
|
60
|
-
return {
|
|
61
|
-
exitCode: 1,
|
|
62
|
-
stderr: ["Treeseed API login expired before approval completed."]
|
|
63
|
-
};
|
|
64
76
|
};
|
|
65
77
|
export {
|
|
66
78
|
handleAuthLogin
|
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
import {
|
|
2
2
|
clearTreeseedRemoteSession,
|
|
3
|
-
resolveTreeseedRemoteConfig
|
|
3
|
+
resolveTreeseedRemoteConfig,
|
|
4
|
+
TreeseedKeyAgentError
|
|
4
5
|
} from "@treeseed/sdk/workflow-support";
|
|
5
6
|
import { guidedResult } from "./utils.js";
|
|
6
7
|
const handleAuthLogout = async (invocation, context) => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
try {
|
|
9
|
+
const tenantRoot = context.cwd;
|
|
10
|
+
const remoteConfig = resolveTreeseedRemoteConfig(tenantRoot, context.env);
|
|
11
|
+
const hostId = typeof invocation.args.host === "string" ? invocation.args.host : remoteConfig.activeHostId;
|
|
12
|
+
clearTreeseedRemoteSession(tenantRoot, hostId);
|
|
13
|
+
return guidedResult({
|
|
14
|
+
command: "auth:logout",
|
|
15
|
+
summary: "Cleared the local Treeseed API session.",
|
|
16
|
+
facts: [{ label: "Host", value: hostId }],
|
|
17
|
+
report: { hostId }
|
|
18
|
+
});
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (error instanceof TreeseedKeyAgentError) {
|
|
21
|
+
return {
|
|
22
|
+
exitCode: 1,
|
|
23
|
+
stderr: [error.message],
|
|
24
|
+
report: { command: "auth:logout", ok: false, code: error.code, details: error.details ?? null }
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
17
29
|
};
|
|
18
30
|
export {
|
|
19
31
|
handleAuthLogout
|
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
import { RemoteTreeseedAuthClient, RemoteTreeseedClient } from "@treeseed/sdk/remote";
|
|
2
|
-
import { resolveTreeseedRemoteConfig } from "@treeseed/sdk/workflow-support";
|
|
2
|
+
import { resolveTreeseedRemoteConfig, TreeseedKeyAgentError } from "@treeseed/sdk/workflow-support";
|
|
3
3
|
import { guidedResult } from "./utils.js";
|
|
4
4
|
const handleAuthWhoAmI = async (_invocation, context) => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
5
|
+
try {
|
|
6
|
+
const remoteConfig = resolveTreeseedRemoteConfig(context.cwd, context.env);
|
|
7
|
+
const client = new RemoteTreeseedAuthClient(new RemoteTreeseedClient(remoteConfig));
|
|
8
|
+
const response = await client.whoAmI();
|
|
9
|
+
return guidedResult({
|
|
10
|
+
command: "auth:whoami",
|
|
11
|
+
summary: "Treeseed API identity",
|
|
12
|
+
facts: [
|
|
13
|
+
{ label: "Host", value: remoteConfig.activeHostId },
|
|
14
|
+
{ label: "Principal", value: response.payload.displayName ?? response.payload.id },
|
|
15
|
+
{ label: "Scopes", value: response.payload.scopes.join(", ") }
|
|
16
|
+
],
|
|
17
|
+
report: {
|
|
18
|
+
hostId: remoteConfig.activeHostId,
|
|
19
|
+
principal: response.payload
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (error instanceof TreeseedKeyAgentError) {
|
|
24
|
+
return {
|
|
25
|
+
exitCode: 1,
|
|
26
|
+
stderr: [error.message],
|
|
27
|
+
report: { command: "auth:whoami", ok: false, code: error.code, details: error.details ?? null }
|
|
28
|
+
};
|
|
19
29
|
}
|
|
20
|
-
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
21
32
|
};
|
|
22
33
|
export {
|
|
23
34
|
handleAuthWhoAmI
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import { type UiViewportLayout } from '../ui/framework.js';
|
|
2
|
-
type ConfigScope = '
|
|
2
|
+
type ConfigScope = 'local' | 'staging' | 'prod';
|
|
3
3
|
export type ConfigViewMode = 'startup' | 'full';
|
|
4
|
+
type ConfigValidation = {
|
|
5
|
+
kind: 'string' | 'nonempty' | 'boolean' | 'number' | 'url' | 'email';
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'enum';
|
|
8
|
+
values: string[];
|
|
9
|
+
};
|
|
4
10
|
type ConfigEntry = {
|
|
5
11
|
id: string;
|
|
6
12
|
label: string;
|
|
7
13
|
group: string;
|
|
14
|
+
cluster: string;
|
|
15
|
+
startupProfile: 'core' | 'optional' | 'advanced';
|
|
16
|
+
requirement: 'required' | 'conditional' | 'optional';
|
|
8
17
|
description: string;
|
|
9
18
|
howToGet: string;
|
|
10
19
|
sensitivity: 'secret' | 'plain' | 'derived';
|
|
11
20
|
targets: string[];
|
|
12
21
|
purposes: string[];
|
|
13
22
|
storage: 'shared' | 'scoped';
|
|
23
|
+
validation?: ConfigValidation;
|
|
14
24
|
scope: Exclude<ConfigScope, 'all'>;
|
|
15
25
|
sharedScopes: Array<Exclude<ConfigScope, 'all'>>;
|
|
16
26
|
required: boolean;
|
|
@@ -25,23 +35,28 @@ type ConfigContextSnapshot = {
|
|
|
25
35
|
};
|
|
26
36
|
scopes: Array<Exclude<ConfigScope, 'all'>>;
|
|
27
37
|
entriesByScope: Record<Exclude<ConfigScope, 'all'>, ConfigEntry[]>;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
configReadinessByScope: Record<Exclude<ConfigScope, 'all'>, {
|
|
39
|
+
github: {
|
|
40
|
+
configured: boolean;
|
|
31
41
|
};
|
|
32
|
-
|
|
33
|
-
|
|
42
|
+
cloudflare: {
|
|
43
|
+
configured: boolean;
|
|
34
44
|
};
|
|
35
45
|
railway: {
|
|
36
|
-
|
|
46
|
+
configured: boolean;
|
|
47
|
+
};
|
|
48
|
+
localDevelopment: {
|
|
49
|
+
configured: boolean;
|
|
37
50
|
};
|
|
38
51
|
}>;
|
|
39
52
|
};
|
|
40
53
|
export type ConfigPage = {
|
|
54
|
+
kind: 'entry';
|
|
41
55
|
key: string;
|
|
42
56
|
entry: ConfigEntry;
|
|
43
|
-
scope:
|
|
44
|
-
scopes:
|
|
57
|
+
scope: ConfigScope;
|
|
58
|
+
scopes: ConfigScope[];
|
|
59
|
+
requiredScopes: ConfigScope[];
|
|
45
60
|
required: boolean;
|
|
46
61
|
currentValue: string;
|
|
47
62
|
suggestedValue: string;
|
|
@@ -56,6 +71,15 @@ export type ConfigEditorResult = {
|
|
|
56
71
|
overrides: Record<string, string>;
|
|
57
72
|
viewMode: ConfigViewMode;
|
|
58
73
|
};
|
|
74
|
+
type ConfigCommitUpdate = {
|
|
75
|
+
scope: Exclude<ConfigScope, 'all'>;
|
|
76
|
+
entryId: string;
|
|
77
|
+
value: string;
|
|
78
|
+
};
|
|
79
|
+
export type ConfigInputState = {
|
|
80
|
+
value: string;
|
|
81
|
+
cursor: number;
|
|
82
|
+
};
|
|
59
83
|
export type ConfigViewportLayout = UiViewportLayout & {
|
|
60
84
|
sidebarWidth: number;
|
|
61
85
|
contentWidth: number;
|
|
@@ -64,9 +88,41 @@ export type ConfigViewportLayout = UiViewportLayout & {
|
|
|
64
88
|
inputHeight: number;
|
|
65
89
|
actionRowHeight: number;
|
|
66
90
|
};
|
|
91
|
+
export declare function filterCliConfigPages(pages: ConfigPage[], query: string): ConfigPage[];
|
|
67
92
|
export declare function computeConfigViewportLayout(rows: number, columns: number): ConfigViewportLayout;
|
|
68
93
|
export declare function buildCliConfigPages(context: ConfigContextSnapshot, selectedFilter: ConfigScope, overrides?: Record<string, string>, viewMode?: ConfigViewMode): ConfigPage[];
|
|
94
|
+
export declare function normalizeConfigInputChunk(input: string): string;
|
|
95
|
+
export declare function applyConfigInputInsertion(state: ConfigInputState, input: string): ConfigInputState;
|
|
96
|
+
export declare function readLinuxClipboardText(): string | null;
|
|
69
97
|
export declare function runCliConfigEditor(context: ConfigContextSnapshot, options?: {
|
|
70
98
|
initialViewMode?: ConfigViewMode;
|
|
99
|
+
mouseEnabled?: boolean;
|
|
100
|
+
initialStatusMessage?: string;
|
|
101
|
+
toolAvailability?: {
|
|
102
|
+
githubCli?: {
|
|
103
|
+
available: boolean;
|
|
104
|
+
};
|
|
105
|
+
wranglerCli?: {
|
|
106
|
+
available: boolean;
|
|
107
|
+
};
|
|
108
|
+
railwayCli?: {
|
|
109
|
+
available: boolean;
|
|
110
|
+
};
|
|
111
|
+
ghActExtension?: {
|
|
112
|
+
available: boolean;
|
|
113
|
+
};
|
|
114
|
+
dockerDaemon?: {
|
|
115
|
+
available: boolean;
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
secretSession?: {
|
|
119
|
+
status?: {
|
|
120
|
+
unlocked?: boolean;
|
|
121
|
+
};
|
|
122
|
+
createdWrappedKey?: boolean;
|
|
123
|
+
migratedWrappedKey?: boolean;
|
|
124
|
+
unlockSource?: string;
|
|
125
|
+
};
|
|
126
|
+
onCommit?: (update: ConfigCommitUpdate) => Promise<ConfigContextSnapshot> | ConfigContextSnapshot;
|
|
71
127
|
}): Promise<ConfigEditorResult | null>;
|
|
72
128
|
export {};
|