@treeseed/cli 0.4.11 → 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.
Files changed (40) hide show
  1. package/README.md +35 -8
  2. package/dist/cli/handlers/auth-login.js +58 -46
  3. package/dist/cli/handlers/auth-logout.js +23 -11
  4. package/dist/cli/handlers/auth-whoami.js +27 -16
  5. package/dist/cli/handlers/close.js +9 -4
  6. package/dist/cli/handlers/config-ui.d.ts +65 -9
  7. package/dist/cli/handlers/config-ui.js +561 -175
  8. package/dist/cli/handlers/config.js +164 -10
  9. package/dist/cli/handlers/destroy.js +3 -2
  10. package/dist/cli/handlers/dev.js +6 -1
  11. package/dist/cli/handlers/doctor.js +82 -47
  12. package/dist/cli/handlers/recover.d.ts +2 -0
  13. package/dist/cli/handlers/recover.js +25 -0
  14. package/dist/cli/handlers/release.js +14 -4
  15. package/dist/cli/handlers/resume.d.ts +2 -0
  16. package/dist/cli/handlers/resume.js +23 -0
  17. package/dist/cli/handlers/save.js +9 -3
  18. package/dist/cli/handlers/secret-prompts.d.ts +2 -0
  19. package/dist/cli/handlers/secret-prompts.js +54 -0
  20. package/dist/cli/handlers/secrets.d.ts +7 -0
  21. package/dist/cli/handlers/secrets.js +161 -0
  22. package/dist/cli/handlers/stage.js +10 -4
  23. package/dist/cli/handlers/status.js +37 -5
  24. package/dist/cli/handlers/switch.js +11 -4
  25. package/dist/cli/handlers/tasks.js +1 -0
  26. package/dist/cli/handlers/utils.js +15 -8
  27. package/dist/cli/handlers/workflow.js +74 -3
  28. package/dist/cli/help-ui.js +1 -1
  29. package/dist/cli/operations-registry.js +200 -21
  30. package/dist/cli/registry.d.ts +8 -0
  31. package/dist/cli/registry.js +19 -1
  32. package/dist/cli/repair.js +5 -9
  33. package/dist/cli/ui/framework.d.ts +2 -0
  34. package/dist/cli/ui/framework.js +53 -22
  35. package/dist/cli/ui/mouse.d.ts +3 -1
  36. package/dist/cli/ui/mouse.js +3 -3
  37. package/package.json +7 -6
  38. package/scripts/verify-driver.mjs +34 -0
  39. package/dist/cli/workflow-state.d.ts +0 -58
  40. package/dist/cli/workflow-state.js +0 -195
@@ -41,7 +41,7 @@ function parseTerminalMouseInput(input) {
41
41
  }
42
42
  return events;
43
43
  }
44
- function useTerminalMouse(onEvent) {
44
+ function useTerminalMouse(onEvent, options = {}) {
45
45
  const { stdin } = useStdin();
46
46
  const { stdout } = useStdout();
47
47
  const handlerRef = React.useRef(onEvent);
@@ -49,7 +49,7 @@ function useTerminalMouse(onEvent) {
49
49
  handlerRef.current = onEvent;
50
50
  }, [onEvent]);
51
51
  React.useEffect(() => {
52
- if (!stdin || !stdout || !process.stdin.isTTY || !process.stdout.isTTY) {
52
+ if (options.enabled === false || !stdin || !stdout || !process.stdin.isTTY || !process.stdout.isTTY) {
53
53
  return;
54
54
  }
55
55
  stdout.write("\x1B[?1000h\x1B[?1006h");
@@ -64,7 +64,7 @@ function useTerminalMouse(onEvent) {
64
64
  stdin.off("data", handleData);
65
65
  stdout.write("\x1B[?1000l\x1B[?1006l");
66
66
  };
67
- }, [stdin, stdout]);
67
+ }, [options.enabled, stdin, stdout]);
68
68
  }
69
69
  export {
70
70
  parseTerminalMouseInput,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/cli",
3
- "version": "0.4.11",
3
+ "version": "0.5.1",
4
4
  "description": "Operator-facing Treeseed CLI package.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -20,7 +20,8 @@
20
20
  "types": "./dist/index.d.ts",
21
21
  "files": [
22
22
  "README.md",
23
- "dist"
23
+ "dist",
24
+ "scripts/verify-driver.mjs"
24
25
  ],
25
26
  "publishConfig": {
26
27
  "access": "public"
@@ -34,16 +35,16 @@
34
35
  "build:dist": "node ./scripts/run-ts.mjs ./scripts/build-dist.ts",
35
36
  "prepack": "npm run build:dist",
36
37
  "verify:direct": "npm run release:verify",
37
- "verify:local": "node --input-type=module -e \"process.env.TREESEED_VERIFY_DRIVER='direct'; await import('@treeseed/sdk/scripts/verify-driver')\"",
38
- "verify:action": "node --input-type=module -e \"process.env.TREESEED_VERIFY_DRIVER='act'; await import('@treeseed/sdk/scripts/verify-driver')\"",
39
- "verify": "node --input-type=module -e \"await import('@treeseed/sdk/scripts/verify-driver')\"",
38
+ "verify:local": "node --input-type=module -e \"process.env.TREESEED_VERIFY_DRIVER='direct'; await import('./scripts/verify-driver.mjs')\"",
39
+ "verify:action": "node --input-type=module -e \"process.env.TREESEED_VERIFY_DRIVER='act'; await import('./scripts/verify-driver.mjs')\"",
40
+ "verify": "node --input-type=module -e \"await import('./scripts/verify-driver.mjs')\"",
40
41
  "release:setup": "npm run setup:ci",
41
42
  "release:check-tag": "node ./scripts/run-ts.mjs ./scripts/assert-release-tag-version.ts",
42
43
  "release:verify": "node ./scripts/run-ts.mjs ./scripts/release-verify.ts",
43
44
  "release:publish": "node ./scripts/run-ts.mjs ./scripts/publish-package.ts"
44
45
  },
45
46
  "dependencies": {
46
- "@treeseed/sdk": "^0.4.12",
47
+ "@treeseed/sdk": "^0.5.3",
47
48
  "ink": "^7.0.0",
48
49
  "react": "^19.2.5"
49
50
  },
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from 'node:child_process';
4
+
5
+ function runDirectVerify() {
6
+ const result = spawnSync('npm', ['run', 'verify:direct'], {
7
+ cwd: process.cwd(),
8
+ env: process.env,
9
+ stdio: 'inherit',
10
+ });
11
+ process.exit(result.status ?? 1);
12
+ }
13
+
14
+ const entrypointCheckOnly = process.env.TREESEED_VERIFY_ENTRYPOINT_CHECK === 'true';
15
+
16
+ try {
17
+ await import('@treeseed/sdk/scripts/verify-driver');
18
+ if (entrypointCheckOnly) {
19
+ process.exit(0);
20
+ }
21
+ } catch (error) {
22
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ERR_MODULE_NOT_FOUND') {
23
+ if (entrypointCheckOnly) {
24
+ process.stderr.write('Treeseed cli verify: @treeseed/sdk is required for verify entrypoint resolution.\n');
25
+ process.exit(1);
26
+ }
27
+ if (process.env.TREESEED_VERIFY_DRIVER === 'act') {
28
+ process.stderr.write('Treeseed cli verify: `act` mode requires @treeseed/sdk to be installed.\n');
29
+ process.exit(1);
30
+ }
31
+ runDirectVerify();
32
+ }
33
+ throw error;
34
+ }
@@ -1,58 +0,0 @@
1
- export type TreeseedBranchRole = 'feature' | 'staging' | 'main' | 'detached' | 'none';
2
- export type TreeseedWorkflowRecommendation = {
3
- command: string;
4
- reason: string;
5
- };
6
- export type TreeseedWorkflowState = {
7
- cwd: string;
8
- workspaceRoot: boolean;
9
- tenantRoot: boolean;
10
- deployConfigPresent: boolean;
11
- repoRoot: string | null;
12
- branchName: string | null;
13
- branchRole: TreeseedBranchRole;
14
- environment: 'local' | 'staging' | 'prod' | 'none';
15
- dirtyWorktree: boolean;
16
- preview: {
17
- enabled: boolean;
18
- url: string | null;
19
- lastDeploymentTimestamp: string | null;
20
- };
21
- persistentEnvironments: Record<string, {
22
- initialized: boolean;
23
- lastValidatedAt: string | null;
24
- lastDeploymentTimestamp: string | null;
25
- lastDeployedUrl: string | null;
26
- }>;
27
- auth: {
28
- gh: boolean;
29
- wrangler: boolean;
30
- railway: boolean;
31
- copilot: boolean;
32
- remoteApi: boolean;
33
- };
34
- managedServices: Record<string, {
35
- enabled: boolean;
36
- initialized: boolean;
37
- lastDeploymentTimestamp: string | null;
38
- lastDeployedUrl: string | null;
39
- provider: string | null;
40
- }>;
41
- files: {
42
- treeseedConfig: boolean;
43
- machineConfig: boolean;
44
- machineKey: boolean;
45
- envLocal: boolean;
46
- devVars: boolean;
47
- };
48
- releaseReady: boolean;
49
- rollbackCandidates: Array<{
50
- scope: 'staging' | 'prod';
51
- commit: string | null;
52
- timestamp: string | null;
53
- url: string | null;
54
- }>;
55
- recommendations: TreeseedWorkflowRecommendation[];
56
- };
57
- export declare function resolveTreeseedWorkflowState(cwd: string): TreeseedWorkflowState;
58
- export declare function recommendTreeseedNextSteps(state: TreeseedWorkflowState): TreeseedWorkflowRecommendation[];
@@ -1,195 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { resolve } from "node:path";
3
- import { getTreeseedMachineConfigPaths, resolveTreeseedRemoteSession } from "@treeseed/sdk/workflow-support";
4
- import {
5
- createBranchPreviewDeployTarget,
6
- createPersistentDeployTarget,
7
- loadDeployState
8
- } from "@treeseed/sdk/workflow-support";
9
- import { PRODUCTION_BRANCH, STAGING_BRANCH } from "@treeseed/sdk/workflow-support";
10
- import { loadCliDeployConfig } from "@treeseed/sdk/workflow-support";
11
- import { collectCliPreflight } from "@treeseed/sdk/workflow-support";
12
- import { currentBranch, gitStatusPorcelain, repoRoot } from "@treeseed/sdk/workflow-support";
13
- import { isWorkspaceRoot } from "@treeseed/sdk/workflow-support";
14
- function safeResolveRepoRoot(cwd) {
15
- try {
16
- return repoRoot(cwd);
17
- } catch {
18
- return null;
19
- }
20
- }
21
- function branchRoleFor(branchName) {
22
- if (!branchName) return "none";
23
- if (branchName === STAGING_BRANCH) return "staging";
24
- if (branchName === PRODUCTION_BRANCH) return "main";
25
- return "feature";
26
- }
27
- function environmentForBranchRole(branchRole) {
28
- if (branchRole === "staging") return "staging";
29
- if (branchRole === "main") return "prod";
30
- if (branchRole === "feature") return "local";
31
- return "none";
32
- }
33
- function emptyPersistentEnvironments() {
34
- return {
35
- local: { initialized: false, lastValidatedAt: null, lastDeploymentTimestamp: null, lastDeployedUrl: null },
36
- staging: { initialized: false, lastValidatedAt: null, lastDeploymentTimestamp: null, lastDeployedUrl: null },
37
- prod: { initialized: false, lastValidatedAt: null, lastDeploymentTimestamp: null, lastDeployedUrl: null }
38
- };
39
- }
40
- function resolveTreeseedWorkflowState(cwd) {
41
- const workspaceRoot = isWorkspaceRoot(cwd);
42
- const treeseedConfigPath = resolve(cwd, "treeseed.site.yaml");
43
- const tenantRoot = existsSync(treeseedConfigPath);
44
- const root = safeResolveRepoRoot(cwd);
45
- const branchName = root ? currentBranch(root) || null : null;
46
- const branchRole = branchRoleFor(branchName);
47
- const dirtyWorktree = root ? gitStatusPorcelain(root).length > 0 : false;
48
- const preflight = collectCliPreflight({ cwd, requireAuth: false });
49
- const { configPath, keyPath } = getTreeseedMachineConfigPaths(cwd);
50
- const state = {
51
- cwd,
52
- workspaceRoot,
53
- tenantRoot,
54
- deployConfigPresent: tenantRoot,
55
- repoRoot: root,
56
- branchName,
57
- branchRole,
58
- environment: environmentForBranchRole(branchRole),
59
- dirtyWorktree,
60
- preview: {
61
- enabled: false,
62
- url: null,
63
- lastDeploymentTimestamp: null
64
- },
65
- persistentEnvironments: emptyPersistentEnvironments(),
66
- auth: {
67
- gh: preflight.checks.auth.gh?.authenticated === true,
68
- wrangler: preflight.checks.auth.wrangler?.authenticated === true,
69
- railway: preflight.checks.auth.railway?.authenticated === true,
70
- copilot: preflight.checks.auth.copilot?.configured === true,
71
- remoteApi: Boolean(resolveTreeseedRemoteSession(cwd))
72
- },
73
- managedServices: {
74
- api: { enabled: false, initialized: false, lastDeploymentTimestamp: null, lastDeployedUrl: null, provider: null },
75
- agents: { enabled: false, initialized: false, lastDeploymentTimestamp: null, lastDeployedUrl: null, provider: null },
76
- manager: { enabled: false, initialized: false, lastDeploymentTimestamp: null, lastDeployedUrl: null, provider: null },
77
- worker: { enabled: false, initialized: false, lastDeploymentTimestamp: null, lastDeployedUrl: null, provider: null },
78
- workdayStart: { enabled: false, initialized: false, lastDeploymentTimestamp: null, lastDeployedUrl: null, provider: null },
79
- workdayReport: { enabled: false, initialized: false, lastDeploymentTimestamp: null, lastDeployedUrl: null, provider: null }
80
- },
81
- files: {
82
- treeseedConfig: tenantRoot,
83
- machineConfig: existsSync(configPath),
84
- machineKey: existsSync(keyPath),
85
- envLocal: existsSync(resolve(cwd, ".env.local")),
86
- devVars: existsSync(resolve(cwd, ".dev.vars"))
87
- },
88
- releaseReady: branchRole === "staging" && !dirtyWorktree,
89
- rollbackCandidates: [],
90
- recommendations: []
91
- };
92
- if (tenantRoot) {
93
- try {
94
- const deployConfig = loadCliDeployConfig(cwd);
95
- for (const scope of ["local", "staging", "prod"]) {
96
- const deployState = loadDeployState(cwd, deployConfig, { target: createPersistentDeployTarget(scope) });
97
- state.persistentEnvironments[scope] = {
98
- initialized: deployState.readiness?.initialized === true || scope === "local",
99
- lastValidatedAt: deployState.readiness?.lastValidatedAt ?? deployState.readiness?.initializedAt ?? null,
100
- lastDeploymentTimestamp: deployState.lastDeploymentTimestamp ?? null,
101
- lastDeployedUrl: deployState.lastDeployedUrl ?? null
102
- };
103
- if (scope !== "local") {
104
- const history = Array.isArray(deployState.deploymentHistory) ? deployState.deploymentHistory ?? [] : [];
105
- const latestHistory = history.at(-1) ?? null;
106
- state.rollbackCandidates.push({
107
- scope,
108
- commit: typeof latestHistory?.commit === "string" ? latestHistory.commit : deployState.lastDeployedCommit ?? null,
109
- timestamp: typeof latestHistory?.timestamp === "string" ? latestHistory.timestamp : deployState.lastDeploymentTimestamp ?? null,
110
- url: typeof latestHistory?.url === "string" ? latestHistory.url : deployState.lastDeployedUrl ?? null
111
- });
112
- }
113
- for (const serviceKey of ["api", "agents", "manager", "worker", "workdayStart", "workdayReport"]) {
114
- const service = deployState.services?.[serviceKey];
115
- if (!service) continue;
116
- state.managedServices[serviceKey] = {
117
- enabled: service.enabled === true,
118
- initialized: service.initialized === true,
119
- lastDeploymentTimestamp: service.lastDeploymentTimestamp ?? null,
120
- lastDeployedUrl: service.lastDeployedUrl ?? service.publicBaseUrl ?? null,
121
- provider: service.provider ?? null
122
- };
123
- }
124
- }
125
- if (branchRole === "feature" && branchName) {
126
- const previewState = loadDeployState(cwd, deployConfig, { target: createBranchPreviewDeployTarget(branchName) });
127
- state.preview = {
128
- enabled: previewState.previewEnabled === true || previewState.readiness?.initialized === true,
129
- url: previewState.lastDeployedUrl ?? null,
130
- lastDeploymentTimestamp: previewState.lastDeploymentTimestamp ?? null
131
- };
132
- }
133
- } catch {
134
- }
135
- }
136
- state.recommendations = recommendTreeseedNextSteps(state);
137
- return state;
138
- }
139
- function recommendTreeseedNextSteps(state) {
140
- const recommendations = [];
141
- if (!state.workspaceRoot) {
142
- return [{ command: "treeseed status", reason: "Run this command from inside a Treeseed workspace so the CLI can resolve the project root." }];
143
- }
144
- if (!state.deployConfigPresent) {
145
- return [{ command: "treeseed init <directory>", reason: "Create a new Treeseed tenant before configuring or releasing anything." }];
146
- }
147
- if (!state.files.machineConfig) {
148
- recommendations.push({ command: "treeseed status", reason: "Validate tooling, auth, and repository readiness first." });
149
- recommendations.push({ command: "treeseed config", reason: "Bootstrap the local machine config and local environment files." });
150
- return recommendations;
151
- }
152
- if (state.branchRole === "feature") {
153
- if (state.dirtyWorktree) {
154
- recommendations.push({ command: 'treeseed save "describe your change"', reason: "Persist, verify, and push the current task branch before staging or closing it." });
155
- } else {
156
- recommendations.push({ command: 'treeseed stage "describe the resolution"', reason: "Merge this task branch into staging and clean up branch artifacts." });
157
- }
158
- if (state.preview.enabled && state.branchName) {
159
- recommendations.push({ command: 'treeseed save "describe your change"', reason: "Save refreshes the branch preview deployment when one is enabled." });
160
- } else {
161
- recommendations.push({ command: "treeseed dev", reason: "Use the local environment for iterative work on this feature branch." });
162
- }
163
- recommendations.push({ command: 'treeseed close "reason"', reason: "Archive this task without merging if it should be abandoned." });
164
- return recommendations.slice(0, 3);
165
- }
166
- if (state.branchRole === "staging") {
167
- if (!state.persistentEnvironments.staging.initialized) {
168
- recommendations.push({ command: "treeseed config --environment staging", reason: "Initialize the staging environment before releasing." });
169
- } else {
170
- recommendations.push({ command: "treeseed release --patch", reason: "Promote staging into main when the integration branch is ready for production." });
171
- if (state.managedServices.api.enabled || state.managedServices.agents.enabled) {
172
- recommendations.push({ command: "treeseed auth:login", reason: "Keep the local CLI authenticated to the remote API used by managed services." });
173
- }
174
- }
175
- return recommendations.slice(0, 3);
176
- }
177
- if (state.branchRole === "main") {
178
- if (state.dirtyWorktree) {
179
- recommendations.push({ command: 'treeseed save --hotfix "describe the hotfix"', reason: "Only explicit hotfix saves are allowed on main." });
180
- } else if (!state.persistentEnvironments.prod.initialized) {
181
- recommendations.push({ command: "treeseed config --environment prod", reason: "Initialize production before a release requires it." });
182
- } else {
183
- recommendations.push({ command: "treeseed status", reason: "Inspect production state and release readiness." });
184
- recommendations.push({ command: "treeseed rollback prod", reason: "Roll back production to the previous recorded deployment if needed." });
185
- }
186
- return recommendations.slice(0, 3);
187
- }
188
- recommendations.push({ command: "treeseed dev", reason: "Start the local Treeseed development environment." });
189
- recommendations.push({ command: "treeseed switch feature/my-change", reason: "Create a task branch from the latest staging commit." });
190
- return recommendations.slice(0, 3);
191
- }
192
- export {
193
- recommendTreeseedNextSteps,
194
- resolveTreeseedWorkflowState
195
- };