@treeseed/cli 0.4.12 → 0.5.1

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.
@@ -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
- const tenantRoot = context.cwd;
12
- const remoteConfig = resolveTreeseedRemoteConfig(tenantRoot, context.env);
13
- const hostId = typeof invocation.args.host === "string" ? invocation.args.host : remoteConfig.activeHostId;
14
- const client = new RemoteTreeseedAuthClient(new RemoteTreeseedClient({
15
- ...remoteConfig,
16
- activeHostId: hostId
17
- }));
18
- const started = await client.startDeviceFlow({
19
- clientName: "treeseed-cli",
20
- scopes: ["auth:me", "sdk", "operations"]
21
- });
22
- if (context.outputFormat !== "json") {
23
- context.write(`Open ${started.verificationUriComplete}`, "stdout");
24
- context.write(`User code: ${started.userCode}`, "stdout");
25
- context.write("Waiting for approval...", "stdout");
26
- }
27
- const deadline = Date.parse(started.expiresAt);
28
- while (Date.now() < deadline) {
29
- const response = await client.pollDeviceFlow({ deviceCode: started.deviceCode });
30
- if (response.ok && response.status === "approved") {
31
- setTreeseedRemoteSession(tenantRoot, {
32
- hostId,
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
- if (!response.ok && response.status !== "already_used") {
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: [response.error]
70
+ stderr: [error.message],
71
+ report: { command: "auth:login", ok: false, code: error.code, details: error.details ?? null }
56
72
  };
57
73
  }
58
- await sleep(started.intervalSeconds * 1e3);
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
- const tenantRoot = context.cwd;
8
- const remoteConfig = resolveTreeseedRemoteConfig(tenantRoot, context.env);
9
- const hostId = typeof invocation.args.host === "string" ? invocation.args.host : remoteConfig.activeHostId;
10
- clearTreeseedRemoteSession(tenantRoot, hostId);
11
- return guidedResult({
12
- command: "auth:logout",
13
- summary: "Cleared the local Treeseed API session.",
14
- facts: [{ label: "Host", value: hostId }],
15
- report: { hostId }
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
- const remoteConfig = resolveTreeseedRemoteConfig(context.cwd, context.env);
6
- const client = new RemoteTreeseedAuthClient(new RemoteTreeseedClient(remoteConfig));
7
- const response = await client.whoAmI();
8
- return guidedResult({
9
- command: "auth:whoami",
10
- summary: "Treeseed API identity",
11
- facts: [
12
- { label: "Host", value: remoteConfig.activeHostId },
13
- { label: "Principal", value: response.payload.displayName ?? response.payload.id },
14
- { label: "Scopes", value: response.payload.scopes.join(", ") }
15
- ],
16
- report: {
17
- hostId: remoteConfig.activeHostId,
18
- principal: response.payload
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 = 'all' | 'local' | 'staging' | 'prod';
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
- authStatusByScope: Record<Exclude<ConfigScope, 'all'>, {
29
- gh: {
30
- authenticated: boolean;
38
+ configReadinessByScope: Record<Exclude<ConfigScope, 'all'>, {
39
+ github: {
40
+ configured: boolean;
31
41
  };
32
- wrangler: {
33
- authenticated: boolean;
42
+ cloudflare: {
43
+ configured: boolean;
34
44
  };
35
45
  railway: {
36
- authenticated: boolean;
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: Exclude<ConfigScope, 'all'>;
44
- scopes: Array<Exclude<ConfigScope, 'all'>>;
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 {};