replicas-cli 0.2.60 → 0.2.61

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 (2) hide show
  1. package/dist/index.mjs +774 -32
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  // src/index.ts
21
21
  import "dotenv/config";
22
22
  import { Command } from "commander";
23
- import chalk18 from "chalk";
23
+ import chalk19 from "chalk";
24
24
 
25
25
  // src/commands/login.ts
26
26
  import http from "http";
@@ -1733,8 +1733,662 @@ Repositories (${response.repositories.length}):
1733
1733
  }
1734
1734
  }
1735
1735
 
1736
- // src/commands/preview.ts
1736
+ // src/commands/automation.ts
1737
1737
  import chalk16 from "chalk";
1738
+ import prompts4 from "prompts";
1739
+ function formatDate3(dateString) {
1740
+ return new Date(dateString).toLocaleString();
1741
+ }
1742
+ function formatTrigger(trigger) {
1743
+ if (trigger.type === "cron") {
1744
+ const config2 = trigger.config;
1745
+ const tz = config2.timezone ? ` (${config2.timezone})` : "";
1746
+ return `cron: ${config2.schedule}${tz}`;
1747
+ }
1748
+ if (trigger.type === "github") {
1749
+ const config2 = trigger.config;
1750
+ const repoFilter = config2.repository_ids?.length ? ` (${config2.repository_ids.length} repo${config2.repository_ids.length > 1 ? "s" : ""} filtered)` : "";
1751
+ return `github: ${config2.event}${repoFilter}`;
1752
+ }
1753
+ return `${trigger.type}`;
1754
+ }
1755
+ function truncate2(text, maxLength) {
1756
+ if (text.length <= maxLength) return text;
1757
+ return text.substring(0, maxLength) + "...";
1758
+ }
1759
+ function printAutomation(automation2) {
1760
+ console.log(chalk16.white(` ${automation2.name}`));
1761
+ console.log(chalk16.gray(` ID: ${automation2.id}`));
1762
+ if (automation2.description) {
1763
+ console.log(chalk16.gray(` Description: ${automation2.description}`));
1764
+ }
1765
+ console.log(chalk16.gray(` Enabled: ${automation2.enabled ? chalk16.green("yes") : chalk16.red("no")}`));
1766
+ if (automation2.triggers.length > 0) {
1767
+ console.log(chalk16.gray(` Triggers: ${automation2.triggers.map(formatTrigger).join(", ")}`));
1768
+ }
1769
+ console.log(chalk16.gray(` Prompt: ${truncate2(automation2.prompt, 80)}`));
1770
+ if (automation2.cron_next_fire_at) {
1771
+ console.log(chalk16.gray(` Next Run: ${formatDate3(automation2.cron_next_fire_at)}`));
1772
+ }
1773
+ if (automation2.workspace_lifecycle_policy && automation2.workspace_lifecycle_policy !== "default") {
1774
+ const lifecycle = automation2.workspace_lifecycle_policy === "delete_after_inactivity" ? `delete_after_inactivity (${automation2.workspace_auto_stop_minutes ?? 30}m)` : automation2.workspace_lifecycle_policy;
1775
+ console.log(chalk16.gray(` Lifecycle: ${lifecycle}`));
1776
+ }
1777
+ console.log(chalk16.gray(` Created: ${formatDate3(automation2.created_at)}`));
1778
+ console.log(chalk16.gray(` Updated: ${formatDate3(automation2.updated_at)}`));
1779
+ console.log();
1780
+ }
1781
+ function ensureAuthenticated() {
1782
+ if (!isAuthenticated()) {
1783
+ console.log(chalk16.red('Not logged in. Please run "replicas login" first.'));
1784
+ process.exit(1);
1785
+ }
1786
+ }
1787
+ async function automationListCommand(options) {
1788
+ ensureAuthenticated();
1789
+ try {
1790
+ const params = new URLSearchParams();
1791
+ if (options.page) params.set("page", options.page);
1792
+ if (options.limit) params.set("limit", options.limit);
1793
+ const query = params.toString();
1794
+ const response = await orgAuthenticatedFetch(
1795
+ `/v1/automations${query ? "?" + query : ""}`
1796
+ );
1797
+ if (response.automations.length === 0) {
1798
+ console.log(chalk16.yellow("\nNo automations found.\n"));
1799
+ return;
1800
+ }
1801
+ console.log(chalk16.green(`
1802
+ Automations (Page ${response.page} of ${response.totalPages}, Total: ${response.total}):
1803
+ `));
1804
+ for (const automation2 of response.automations) {
1805
+ printAutomation(automation2);
1806
+ }
1807
+ } catch (error) {
1808
+ console.error(chalk16.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1809
+ process.exit(1);
1810
+ }
1811
+ }
1812
+ async function automationGetCommand(id) {
1813
+ ensureAuthenticated();
1814
+ try {
1815
+ const response = await orgAuthenticatedFetch(`/v1/automations/${id}`);
1816
+ const automation2 = response.automation;
1817
+ console.log(chalk16.green(`
1818
+ Automation: ${automation2.name}
1819
+ `));
1820
+ console.log(chalk16.gray(` ID: ${automation2.id}`));
1821
+ if (automation2.description) {
1822
+ console.log(chalk16.gray(` Description: ${automation2.description}`));
1823
+ }
1824
+ console.log(chalk16.gray(` Enabled: ${automation2.enabled ? chalk16.green("yes") : chalk16.red("no")}`));
1825
+ if (automation2.triggers.length > 0) {
1826
+ console.log(chalk16.gray(` Triggers:`));
1827
+ for (const trigger of automation2.triggers) {
1828
+ console.log(chalk16.gray(` - ${formatTrigger(trigger)}`));
1829
+ }
1830
+ }
1831
+ console.log(chalk16.gray(` Prompt: ${automation2.prompt}`));
1832
+ if (automation2.repository_ids.length > 0) {
1833
+ console.log(chalk16.gray(` Repository IDs: ${automation2.repository_ids.join(", ")}`));
1834
+ }
1835
+ if (automation2.repository_set_id) {
1836
+ console.log(chalk16.gray(` Repository Set: ${automation2.repository_set_id}`));
1837
+ }
1838
+ if (automation2.cron_expression) {
1839
+ console.log(chalk16.gray(` Cron: ${automation2.cron_expression}`));
1840
+ }
1841
+ if (automation2.cron_timezone) {
1842
+ console.log(chalk16.gray(` Timezone: ${automation2.cron_timezone}`));
1843
+ }
1844
+ if (automation2.cron_next_fire_at) {
1845
+ console.log(chalk16.gray(` Next Run: ${formatDate3(automation2.cron_next_fire_at)}`));
1846
+ }
1847
+ if (automation2.workspace_lifecycle_policy) {
1848
+ console.log(chalk16.gray(` Lifecycle Policy: ${automation2.workspace_lifecycle_policy}`));
1849
+ }
1850
+ if (automation2.workspace_auto_stop_minutes) {
1851
+ console.log(chalk16.gray(` Auto-stop: ${automation2.workspace_auto_stop_minutes} minutes`));
1852
+ }
1853
+ console.log(chalk16.gray(` Created: ${formatDate3(automation2.created_at)}`));
1854
+ console.log(chalk16.gray(` Updated: ${formatDate3(automation2.updated_at)}`));
1855
+ console.log();
1856
+ } catch (error) {
1857
+ console.error(chalk16.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1858
+ process.exit(1);
1859
+ }
1860
+ }
1861
+ async function promptForTriggers(repositories) {
1862
+ const triggers = [];
1863
+ let addMore = true;
1864
+ while (addMore) {
1865
+ const typeResponse = await prompts4({
1866
+ type: "select",
1867
+ name: "type",
1868
+ message: "Select trigger type:",
1869
+ choices: [
1870
+ { title: "Cron (scheduled)", value: "cron" },
1871
+ { title: "GitHub (event-based)", value: "github" }
1872
+ ]
1873
+ });
1874
+ if (!typeResponse.type) return triggers;
1875
+ if (typeResponse.type === "cron") {
1876
+ const scheduleResponse = await prompts4({
1877
+ type: "text",
1878
+ name: "schedule",
1879
+ message: 'Enter cron expression (e.g. "0 9 * * 1-5" for weekdays at 9am):',
1880
+ validate: (value) => value.trim() ? true : "Schedule is required"
1881
+ });
1882
+ if (!scheduleResponse.schedule) return triggers;
1883
+ const tzResponse = await prompts4({
1884
+ type: "text",
1885
+ name: "timezone",
1886
+ message: "Enter timezone (default: UTC):",
1887
+ initial: "UTC"
1888
+ });
1889
+ triggers.push({
1890
+ type: "cron",
1891
+ config: {
1892
+ schedule: scheduleResponse.schedule,
1893
+ timezone: tzResponse.timezone || "UTC"
1894
+ }
1895
+ });
1896
+ } else if (typeResponse.type === "github") {
1897
+ const eventResponse = await prompts4({
1898
+ type: "select",
1899
+ name: "event",
1900
+ message: "Select GitHub event:",
1901
+ choices: [
1902
+ { title: "Pull Request Opened", value: "pull_request.opened" },
1903
+ { title: "Pull Request Synchronized (new push)", value: "pull_request.synchronize" },
1904
+ { title: "Pull Request Reopened", value: "pull_request.reopened" },
1905
+ { title: "Push", value: "push" }
1906
+ ]
1907
+ });
1908
+ if (!eventResponse.event) return triggers;
1909
+ let triggerRepoIds;
1910
+ if (repositories && repositories.length > 0) {
1911
+ const filterResponse = await prompts4({
1912
+ type: "multiselect",
1913
+ name: "repos",
1914
+ message: "Filter trigger to specific repositories (press enter to skip for all):",
1915
+ choices: repositories.map((r) => ({ title: r.name, value: r.id }))
1916
+ });
1917
+ if (filterResponse.repos && filterResponse.repos.length > 0) {
1918
+ triggerRepoIds = filterResponse.repos;
1919
+ }
1920
+ }
1921
+ triggers.push({
1922
+ type: "github",
1923
+ config: {
1924
+ event: eventResponse.event,
1925
+ ...triggerRepoIds ? { repository_ids: triggerRepoIds } : {}
1926
+ }
1927
+ });
1928
+ }
1929
+ const moreResponse = await prompts4({
1930
+ type: "confirm",
1931
+ name: "more",
1932
+ message: "Add another trigger?",
1933
+ initial: false
1934
+ });
1935
+ addMore = moreResponse.more === true;
1936
+ }
1937
+ return triggers;
1938
+ }
1939
+ async function automationCreateCommand(name, options) {
1940
+ ensureAuthenticated();
1941
+ if (options.repositories && options.repositorySet) {
1942
+ console.log(chalk16.red("Cannot use both --repositories (-r) and --repository-set (-s). Choose one."));
1943
+ process.exit(1);
1944
+ }
1945
+ const validLifecycles = ["default", "delete_when_done", "delete_after_inactivity"];
1946
+ if (options.lifecycle && !validLifecycles.includes(options.lifecycle)) {
1947
+ console.log(chalk16.red(`Invalid lifecycle policy: ${options.lifecycle}`));
1948
+ console.log(chalk16.gray(`Valid options: ${validLifecycles.join(", ")}`));
1949
+ process.exit(1);
1950
+ }
1951
+ if (options.autoStopMinutes && options.lifecycle !== "delete_after_inactivity") {
1952
+ console.log(chalk16.red("--auto-stop-minutes requires --lifecycle delete_after_inactivity"));
1953
+ process.exit(1);
1954
+ }
1955
+ if (options.autoStopMinutes) {
1956
+ const minutes = parseInt(options.autoStopMinutes, 10);
1957
+ if (isNaN(minutes) || minutes < 3 || minutes > 1440) {
1958
+ console.log(chalk16.red("--auto-stop-minutes must be between 3 and 1440"));
1959
+ process.exit(1);
1960
+ }
1961
+ }
1962
+ try {
1963
+ const repoResponse = await orgAuthenticatedFetch("/v1/repositories");
1964
+ const repositories = repoResponse.repositories;
1965
+ if (repositories.length === 0) {
1966
+ console.log(chalk16.red("No repositories found. Please add a repository first."));
1967
+ process.exit(1);
1968
+ }
1969
+ let automationName = name;
1970
+ if (!automationName) {
1971
+ const response2 = await prompts4({
1972
+ type: "text",
1973
+ name: "name",
1974
+ message: "Enter automation name:",
1975
+ validate: (value) => value.trim() ? true : "Name is required"
1976
+ });
1977
+ if (!response2.name) {
1978
+ console.log(chalk16.yellow("\nCancelled."));
1979
+ return;
1980
+ }
1981
+ automationName = response2.name;
1982
+ }
1983
+ let automationPrompt = options.prompt;
1984
+ if (!automationPrompt) {
1985
+ const response2 = await prompts4({
1986
+ type: "text",
1987
+ name: "prompt",
1988
+ message: "Enter the prompt for this automation:",
1989
+ validate: (value) => value.trim() ? true : "Prompt is required"
1990
+ });
1991
+ if (!response2.prompt) {
1992
+ console.log(chalk16.yellow("\nCancelled."));
1993
+ return;
1994
+ }
1995
+ automationPrompt = response2.prompt;
1996
+ }
1997
+ let selectedRepoIds = [];
1998
+ let selectedRepoSetId;
1999
+ if (options.repositorySet) {
2000
+ const setsResponse = await orgAuthenticatedFetch("/v1/repository-sets");
2001
+ const repoSet = setsResponse.repository_sets.find((s) => s.name === options.repositorySet);
2002
+ if (!repoSet) {
2003
+ console.log(chalk16.red(`Repository set not found: ${options.repositorySet}`));
2004
+ const available = setsResponse.repository_sets.map((s) => s.name).join(", ");
2005
+ console.log(chalk16.gray(`Available: ${available || "(none)"}`));
2006
+ process.exit(1);
2007
+ }
2008
+ selectedRepoSetId = repoSet.id;
2009
+ } else if (options.repositories) {
2010
+ const repoNames = options.repositories.split(",").map((r) => r.trim()).filter(Boolean);
2011
+ for (const repoName of repoNames) {
2012
+ const repo = repositories.find((r) => r.name === repoName);
2013
+ if (!repo) {
2014
+ console.log(chalk16.red(`Repository not found: ${repoName}`));
2015
+ console.log(chalk16.gray(`Available: ${repositories.map((r) => r.name).join(", ")}`));
2016
+ process.exit(1);
2017
+ }
2018
+ selectedRepoIds.push(repo.id);
2019
+ }
2020
+ } else {
2021
+ const setsResponse = await orgAuthenticatedFetch("/v1/repository-sets");
2022
+ const repoSets = setsResponse.repository_sets;
2023
+ let useRepoSet = false;
2024
+ if (repoSets.length > 0) {
2025
+ const modeResponse = await prompts4({
2026
+ type: "select",
2027
+ name: "mode",
2028
+ message: "Select repositories by:",
2029
+ choices: [
2030
+ { title: "Individual repositories", value: "repos" },
2031
+ { title: "Repository set", value: "set" }
2032
+ ]
2033
+ });
2034
+ if (!modeResponse.mode) {
2035
+ console.log(chalk16.yellow("\nCancelled."));
2036
+ return;
2037
+ }
2038
+ useRepoSet = modeResponse.mode === "set";
2039
+ }
2040
+ if (useRepoSet) {
2041
+ const setResponse = await prompts4({
2042
+ type: "select",
2043
+ name: "set",
2044
+ message: "Select repository set:",
2045
+ choices: repoSets.map((s) => ({
2046
+ title: s.name,
2047
+ value: s.id,
2048
+ description: s.description || `${s.repositories.length} repos`
2049
+ }))
2050
+ });
2051
+ if (!setResponse.set) {
2052
+ console.log(chalk16.yellow("\nCancelled."));
2053
+ return;
2054
+ }
2055
+ selectedRepoSetId = setResponse.set;
2056
+ } else {
2057
+ const response2 = await prompts4({
2058
+ type: "multiselect",
2059
+ name: "repositories",
2060
+ message: "Select repositories:",
2061
+ choices: repositories.map((repo) => ({
2062
+ title: repo.name,
2063
+ value: repo.id,
2064
+ description: repo.url
2065
+ })),
2066
+ min: 1
2067
+ });
2068
+ if (!response2.repositories || response2.repositories.length === 0) {
2069
+ console.log(chalk16.yellow("\nCancelled."));
2070
+ return;
2071
+ }
2072
+ selectedRepoIds = response2.repositories;
2073
+ }
2074
+ }
2075
+ let triggers = [];
2076
+ if (options.triggerCron) {
2077
+ triggers.push({
2078
+ type: "cron",
2079
+ config: {
2080
+ schedule: options.triggerCron,
2081
+ timezone: options.triggerCronTimezone || "UTC"
2082
+ }
2083
+ });
2084
+ }
2085
+ if (options.triggerGithub) {
2086
+ let githubRepoIds;
2087
+ if (options.triggerGithubRepos) {
2088
+ githubRepoIds = [];
2089
+ const repoNames = options.triggerGithubRepos.split(",").map((r) => r.trim()).filter(Boolean);
2090
+ for (const repoName of repoNames) {
2091
+ const repo = repositories.find((r) => r.name === repoName);
2092
+ if (!repo) {
2093
+ console.log(chalk16.red(`Repository not found for trigger filter: ${repoName}`));
2094
+ console.log(chalk16.gray(`Available: ${repositories.map((r) => r.name).join(", ")}`));
2095
+ process.exit(1);
2096
+ }
2097
+ githubRepoIds.push(repo.id);
2098
+ }
2099
+ }
2100
+ triggers.push({
2101
+ type: "github",
2102
+ config: {
2103
+ event: options.triggerGithub,
2104
+ ...githubRepoIds ? { repository_ids: githubRepoIds } : {}
2105
+ }
2106
+ });
2107
+ }
2108
+ if (triggers.length === 0) {
2109
+ triggers = await promptForTriggers(repositories.map((r) => ({ id: r.id, name: r.name })));
2110
+ if (triggers.length === 0) {
2111
+ console.log(chalk16.red("At least one trigger is required."));
2112
+ process.exit(1);
2113
+ }
2114
+ }
2115
+ const body = selectedRepoSetId ? {
2116
+ name: automationName,
2117
+ prompt: automationPrompt,
2118
+ repository_set_id: selectedRepoSetId,
2119
+ triggers,
2120
+ enabled: options.enabled !== false,
2121
+ ...options.lifecycle ? { workspace_lifecycle_policy: options.lifecycle } : {},
2122
+ ...options.autoStopMinutes ? { workspace_auto_stop_minutes: parseInt(options.autoStopMinutes, 10) } : {}
2123
+ } : {
2124
+ name: automationName,
2125
+ prompt: automationPrompt,
2126
+ repository_ids: selectedRepoIds,
2127
+ triggers,
2128
+ enabled: options.enabled !== false,
2129
+ ...options.lifecycle ? { workspace_lifecycle_policy: options.lifecycle } : {},
2130
+ ...options.autoStopMinutes ? { workspace_auto_stop_minutes: parseInt(options.autoStopMinutes, 10) } : {}
2131
+ };
2132
+ console.log(chalk16.gray("\nCreating automation..."));
2133
+ const response = await orgAuthenticatedFetch("/v1/automations", {
2134
+ method: "POST",
2135
+ body
2136
+ });
2137
+ const automation2 = response.automation;
2138
+ console.log(chalk16.green(`
2139
+ Created automation: ${automation2.name}`));
2140
+ console.log(chalk16.gray(` ID: ${automation2.id}`));
2141
+ console.log(chalk16.gray(` Enabled: ${automation2.enabled ? "yes" : "no"}`));
2142
+ if (automation2.triggers.length > 0) {
2143
+ console.log(chalk16.gray(` Triggers: ${automation2.triggers.map(formatTrigger).join(", ")}`));
2144
+ }
2145
+ if (automation2.cron_next_fire_at) {
2146
+ console.log(chalk16.gray(` Next Run: ${formatDate3(automation2.cron_next_fire_at)}`));
2147
+ }
2148
+ console.log();
2149
+ } catch (error) {
2150
+ console.error(chalk16.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
2151
+ process.exit(1);
2152
+ }
2153
+ }
2154
+ async function automationEditCommand(id, options) {
2155
+ ensureAuthenticated();
2156
+ if (options.repositories && options.repositorySet) {
2157
+ console.log(chalk16.red("Cannot use both --repositories (-r) and --repository-set (-s). Choose one."));
2158
+ process.exit(1);
2159
+ }
2160
+ const validLifecycles = ["default", "delete_when_done", "delete_after_inactivity"];
2161
+ if (options.lifecycle && !validLifecycles.includes(options.lifecycle)) {
2162
+ console.log(chalk16.red(`Invalid lifecycle policy: ${options.lifecycle}`));
2163
+ console.log(chalk16.gray(`Valid options: ${validLifecycles.join(", ")}`));
2164
+ process.exit(1);
2165
+ }
2166
+ if (options.autoStopMinutes && options.lifecycle !== "delete_after_inactivity") {
2167
+ console.log(chalk16.red("--auto-stop-minutes requires --lifecycle delete_after_inactivity"));
2168
+ process.exit(1);
2169
+ }
2170
+ if (options.autoStopMinutes) {
2171
+ const minutes = parseInt(options.autoStopMinutes, 10);
2172
+ if (isNaN(minutes) || minutes < 3 || minutes > 1440) {
2173
+ console.log(chalk16.red("--auto-stop-minutes must be between 3 and 1440"));
2174
+ process.exit(1);
2175
+ }
2176
+ }
2177
+ try {
2178
+ const existing = await orgAuthenticatedFetch(`/v1/automations/${id}`);
2179
+ const body = {};
2180
+ const hasOptions = options.name || options.prompt || options.enabled !== void 0 || options.triggerCron || options.triggerGithub || options.repositories || options.repositorySet || options.lifecycle || options.autoStopMinutes;
2181
+ if (!hasOptions) {
2182
+ const nameResponse = await prompts4({
2183
+ type: "text",
2184
+ name: "name",
2185
+ message: "Automation name:",
2186
+ initial: existing.automation.name
2187
+ });
2188
+ if (nameResponse.name && nameResponse.name !== existing.automation.name) {
2189
+ body.name = nameResponse.name;
2190
+ }
2191
+ const promptResponse = await prompts4({
2192
+ type: "text",
2193
+ name: "prompt",
2194
+ message: "Prompt:",
2195
+ initial: existing.automation.prompt
2196
+ });
2197
+ if (promptResponse.prompt && promptResponse.prompt !== existing.automation.prompt) {
2198
+ body.prompt = promptResponse.prompt;
2199
+ }
2200
+ const enabledResponse = await prompts4({
2201
+ type: "confirm",
2202
+ name: "enabled",
2203
+ message: "Enabled?",
2204
+ initial: existing.automation.enabled
2205
+ });
2206
+ if (enabledResponse.enabled !== void 0 && enabledResponse.enabled !== existing.automation.enabled) {
2207
+ body.enabled = enabledResponse.enabled;
2208
+ }
2209
+ const editTriggersResponse = await prompts4({
2210
+ type: "confirm",
2211
+ name: "edit",
2212
+ message: "Edit triggers?",
2213
+ initial: false
2214
+ });
2215
+ if (editTriggersResponse.edit) {
2216
+ const triggers = await promptForTriggers();
2217
+ if (triggers.length > 0) {
2218
+ body.triggers = triggers;
2219
+ }
2220
+ }
2221
+ } else {
2222
+ if (options.name) body.name = options.name;
2223
+ if (options.prompt) body.prompt = options.prompt;
2224
+ if (options.enabled !== void 0) {
2225
+ body.enabled = options.enabled === "true";
2226
+ }
2227
+ if (options.triggerCron || options.triggerGithub) {
2228
+ const triggers = [];
2229
+ if (options.triggerCron) {
2230
+ triggers.push({
2231
+ type: "cron",
2232
+ config: {
2233
+ schedule: options.triggerCron,
2234
+ timezone: options.triggerCronTimezone || "UTC"
2235
+ }
2236
+ });
2237
+ }
2238
+ if (options.triggerGithub) {
2239
+ let githubRepoIds;
2240
+ if (options.triggerGithubRepos) {
2241
+ const repoResponse = await orgAuthenticatedFetch("/v1/repositories");
2242
+ githubRepoIds = [];
2243
+ const repoNames = options.triggerGithubRepos.split(",").map((r) => r.trim()).filter(Boolean);
2244
+ for (const repoName of repoNames) {
2245
+ const repo = repoResponse.repositories.find((r) => r.name === repoName);
2246
+ if (!repo) {
2247
+ console.log(chalk16.red(`Repository not found for trigger filter: ${repoName}`));
2248
+ console.log(chalk16.gray(`Available: ${repoResponse.repositories.map((r) => r.name).join(", ")}`));
2249
+ process.exit(1);
2250
+ }
2251
+ githubRepoIds.push(repo.id);
2252
+ }
2253
+ }
2254
+ triggers.push({
2255
+ type: "github",
2256
+ config: {
2257
+ event: options.triggerGithub,
2258
+ ...githubRepoIds ? { repository_ids: githubRepoIds } : {}
2259
+ }
2260
+ });
2261
+ }
2262
+ body.triggers = triggers;
2263
+ }
2264
+ if (options.repositorySet) {
2265
+ const setsResponse = await orgAuthenticatedFetch("/v1/repository-sets");
2266
+ const repoSet = setsResponse.repository_sets.find((s) => s.name === options.repositorySet);
2267
+ if (!repoSet) {
2268
+ console.log(chalk16.red(`Repository set not found: ${options.repositorySet}`));
2269
+ const available = setsResponse.repository_sets.map((s) => s.name).join(", ");
2270
+ console.log(chalk16.gray(`Available: ${available || "(none)"}`));
2271
+ process.exit(1);
2272
+ }
2273
+ body.repository_set_id = repoSet.id;
2274
+ } else if (options.repositories) {
2275
+ const repoResponse = await orgAuthenticatedFetch("/v1/repositories");
2276
+ const repoNames = options.repositories.split(",").map((r) => r.trim()).filter(Boolean);
2277
+ const repoIds = [];
2278
+ for (const repoName of repoNames) {
2279
+ const repo = repoResponse.repositories.find((r) => r.name === repoName);
2280
+ if (!repo) {
2281
+ console.log(chalk16.red(`Repository not found: ${repoName}`));
2282
+ console.log(chalk16.gray(`Available: ${repoResponse.repositories.map((r) => r.name).join(", ")}`));
2283
+ process.exit(1);
2284
+ }
2285
+ repoIds.push(repo.id);
2286
+ }
2287
+ body.repository_ids = repoIds;
2288
+ }
2289
+ if (options.lifecycle) {
2290
+ body.workspace_lifecycle_policy = options.lifecycle;
2291
+ if (options.lifecycle !== "delete_after_inactivity") {
2292
+ body.workspace_auto_stop_minutes = null;
2293
+ }
2294
+ }
2295
+ if (options.autoStopMinutes) {
2296
+ body.workspace_auto_stop_minutes = parseInt(options.autoStopMinutes, 10);
2297
+ }
2298
+ }
2299
+ if (Object.keys(body).length === 0) {
2300
+ console.log(chalk16.yellow("\nNo changes made.\n"));
2301
+ return;
2302
+ }
2303
+ console.log(chalk16.gray("\nUpdating automation..."));
2304
+ const response = await orgAuthenticatedFetch(`/v1/automations/${id}`, {
2305
+ method: "PATCH",
2306
+ body
2307
+ });
2308
+ const automation2 = response.automation;
2309
+ console.log(chalk16.green(`
2310
+ Updated automation: ${automation2.name}`));
2311
+ console.log(chalk16.gray(` ID: ${automation2.id}`));
2312
+ console.log(chalk16.gray(` Enabled: ${automation2.enabled ? "yes" : "no"}`));
2313
+ if (automation2.triggers.length > 0) {
2314
+ console.log(chalk16.gray(` Triggers: ${automation2.triggers.map(formatTrigger).join(", ")}`));
2315
+ }
2316
+ console.log();
2317
+ } catch (error) {
2318
+ console.error(chalk16.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
2319
+ process.exit(1);
2320
+ }
2321
+ }
2322
+ async function automationRunCommand(id) {
2323
+ ensureAuthenticated();
2324
+ try {
2325
+ const existing = await orgAuthenticatedFetch(`/v1/automations/${id}`);
2326
+ const automation2 = existing.automation;
2327
+ const hasCronTrigger = automation2.triggers.some((t) => t.type === "cron");
2328
+ if (!hasCronTrigger) {
2329
+ console.log(chalk16.red("\nManual run is only allowed for automations with a cron trigger."));
2330
+ console.log(chalk16.gray(`This automation has triggers: ${automation2.triggers.map(formatTrigger).join(", ")}`));
2331
+ console.log();
2332
+ process.exit(1);
2333
+ }
2334
+ console.log(chalk16.gray(`
2335
+ Triggering automation "${automation2.name}"...`));
2336
+ const response = await orgAuthenticatedFetch(
2337
+ `/v1/automations/${id}/trigger`,
2338
+ {
2339
+ method: "POST",
2340
+ body: {}
2341
+ }
2342
+ );
2343
+ if (!response.execution_id) {
2344
+ console.log(chalk16.red(`
2345
+ Automation trigger returned no execution ID. The automation may not have started.`));
2346
+ console.log();
2347
+ process.exit(1);
2348
+ }
2349
+ console.log(chalk16.green(`
2350
+ Automation triggered successfully.`));
2351
+ console.log(chalk16.gray(` Execution ID: ${response.execution_id}`));
2352
+ if (response.workspace_id) {
2353
+ console.log(chalk16.gray(` Workspace ID: ${response.workspace_id}`));
2354
+ }
2355
+ console.log();
2356
+ } catch (error) {
2357
+ console.error(chalk16.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
2358
+ process.exit(1);
2359
+ }
2360
+ }
2361
+ async function automationDeleteCommand(id, options) {
2362
+ ensureAuthenticated();
2363
+ try {
2364
+ const existing = await orgAuthenticatedFetch(`/v1/automations/${id}`);
2365
+ const automationName = existing.automation.name;
2366
+ if (!options.force) {
2367
+ const response = await prompts4({
2368
+ type: "confirm",
2369
+ name: "confirm",
2370
+ message: `Are you sure you want to delete automation "${automationName}" (${id})?`,
2371
+ initial: false
2372
+ });
2373
+ if (!response.confirm) {
2374
+ console.log(chalk16.yellow("\nCancelled."));
2375
+ return;
2376
+ }
2377
+ }
2378
+ await orgAuthenticatedFetch(`/v1/automations/${id}`, {
2379
+ method: "DELETE"
2380
+ });
2381
+ console.log(chalk16.green(`
2382
+ Automation "${automationName}" (${id}) deleted.
2383
+ `));
2384
+ } catch (error) {
2385
+ console.error(chalk16.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
2386
+ process.exit(1);
2387
+ }
2388
+ }
2389
+
2390
+ // src/commands/preview.ts
2391
+ import chalk17 from "chalk";
1738
2392
 
1739
2393
  // src/lib/agent-api.ts
1740
2394
  var MONOLITH_URL3 = process.env.MONOLITH_URL || process.env.REPLICAS_MONOLITH_URL || "https://api.replicas.dev";
@@ -1817,11 +2471,11 @@ async function previewListCommand(workspaceId) {
1817
2471
  `/v1/workspaces/${workspaceId}/previews`
1818
2472
  );
1819
2473
  if (result.previews.length === 0) {
1820
- console.log(chalk16.dim("No active previews"));
2474
+ console.log(chalk17.dim("No active previews"));
1821
2475
  return;
1822
2476
  }
1823
2477
  for (const preview2 of result.previews) {
1824
- console.log(` ${chalk16.cyan(String(preview2.port))} \u2192 ${chalk16.underline(preview2.publicUrl)}`);
2478
+ console.log(` ${chalk17.cyan(String(preview2.port))} \u2192 ${chalk17.underline(preview2.publicUrl)}`);
1825
2479
  }
1826
2480
  }
1827
2481
  }
@@ -1837,11 +2491,11 @@ async function previewAddCommand(workspaceId, options) {
1837
2491
  body: { port: portNum, authenticated: options.authenticated ?? false }
1838
2492
  }
1839
2493
  );
1840
- console.log(chalk16.green(`Preview created: ${result.preview.publicUrl}`));
2494
+ console.log(chalk17.green(`Preview created: ${result.preview.publicUrl}`));
1841
2495
  }
1842
2496
 
1843
2497
  // src/commands/interactive.ts
1844
- import chalk17 from "chalk";
2498
+ import chalk18 from "chalk";
1845
2499
  async function interactiveCommand() {
1846
2500
  if (!isAuthenticated()) {
1847
2501
  throw new Error(
@@ -1854,13 +2508,13 @@ async function interactiveCommand() {
1854
2508
  'No organization selected. Please run "replicas org switch" to select an organization.'
1855
2509
  );
1856
2510
  }
1857
- console.log(chalk17.gray("Starting interactive mode..."));
2511
+ console.log(chalk18.gray("Starting interactive mode..."));
1858
2512
  const { launchInteractive } = await import("./interactive-SY66GQDB.mjs");
1859
2513
  await launchInteractive();
1860
2514
  }
1861
2515
 
1862
2516
  // src/index.ts
1863
- var CLI_VERSION = "0.2.60";
2517
+ var CLI_VERSION = "0.2.61";
1864
2518
  var program = new Command();
1865
2519
  program.name("replicas").description("CLI for managing Replicas workspaces").version(CLI_VERSION);
1866
2520
  program.command("login").description("Authenticate with your Replicas account").action(async () => {
@@ -1868,7 +2522,7 @@ program.command("login").description("Authenticate with your Replicas account").
1868
2522
  await loginCommand();
1869
2523
  } catch (error) {
1870
2524
  if (error instanceof Error) {
1871
- console.error(chalk18.red(`
2525
+ console.error(chalk19.red(`
1872
2526
  \u2717 ${error.message}
1873
2527
  `));
1874
2528
  }
@@ -1880,7 +2534,7 @@ program.command("init").description("Create a replicas.json or replicas.yaml con
1880
2534
  initCommand(options);
1881
2535
  } catch (error) {
1882
2536
  if (error instanceof Error) {
1883
- console.error(chalk18.red(`
2537
+ console.error(chalk19.red(`
1884
2538
  \u2717 ${error.message}
1885
2539
  `));
1886
2540
  }
@@ -1892,7 +2546,7 @@ program.command("logout").description("Clear stored credentials").action(() => {
1892
2546
  logoutCommand();
1893
2547
  } catch (error) {
1894
2548
  if (error instanceof Error) {
1895
- console.error(chalk18.red(`
2549
+ console.error(chalk19.red(`
1896
2550
  \u2717 ${error.message}
1897
2551
  `));
1898
2552
  }
@@ -1904,7 +2558,7 @@ program.command("whoami").description("Display current authenticated user").acti
1904
2558
  await whoamiCommand();
1905
2559
  } catch (error) {
1906
2560
  if (error instanceof Error) {
1907
- console.error(chalk18.red(`
2561
+ console.error(chalk19.red(`
1908
2562
  \u2717 ${error.message}
1909
2563
  `));
1910
2564
  }
@@ -1916,7 +2570,7 @@ program.command("codex-auth").description("Authenticate Replicas with your Codex
1916
2570
  await codexAuthCommand(options);
1917
2571
  } catch (error) {
1918
2572
  if (error instanceof Error) {
1919
- console.error(chalk18.red(`
2573
+ console.error(chalk19.red(`
1920
2574
  \u2717 ${error.message}
1921
2575
  `));
1922
2576
  }
@@ -1928,7 +2582,7 @@ program.command("claude-auth").description("Authenticate Replicas with your Clau
1928
2582
  await claudeAuthCommand(options);
1929
2583
  } catch (error) {
1930
2584
  if (error instanceof Error) {
1931
- console.error(chalk18.red(`
2585
+ console.error(chalk19.red(`
1932
2586
  \u2717 ${error.message}
1933
2587
  `));
1934
2588
  }
@@ -1941,7 +2595,7 @@ org.command("switch").description("Switch to a different organization").action(a
1941
2595
  await orgSwitchCommand();
1942
2596
  } catch (error) {
1943
2597
  if (error instanceof Error) {
1944
- console.error(chalk18.red(`
2598
+ console.error(chalk19.red(`
1945
2599
  \u2717 ${error.message}
1946
2600
  `));
1947
2601
  }
@@ -1953,7 +2607,7 @@ org.action(async () => {
1953
2607
  await orgCommand();
1954
2608
  } catch (error) {
1955
2609
  if (error instanceof Error) {
1956
- console.error(chalk18.red(`
2610
+ console.error(chalk19.red(`
1957
2611
  \u2717 ${error.message}
1958
2612
  `));
1959
2613
  }
@@ -1965,7 +2619,7 @@ program.command("connect <workspace-name>").description("Connect to a workspace
1965
2619
  await connectCommand(workspaceName);
1966
2620
  } catch (error) {
1967
2621
  if (error instanceof Error) {
1968
- console.error(chalk18.red(`
2622
+ console.error(chalk19.red(`
1969
2623
  \u2717 ${error.message}
1970
2624
  `));
1971
2625
  }
@@ -1977,7 +2631,7 @@ program.command("code <workspace-name>").description("Open a workspace in VSCode
1977
2631
  await codeCommand(workspaceName);
1978
2632
  } catch (error) {
1979
2633
  if (error instanceof Error) {
1980
- console.error(chalk18.red(`
2634
+ console.error(chalk19.red(`
1981
2635
  \u2717 ${error.message}
1982
2636
  `));
1983
2637
  }
@@ -1990,7 +2644,7 @@ config.command("get <key>").description("Get a configuration value").action(asyn
1990
2644
  await configGetCommand(key);
1991
2645
  } catch (error) {
1992
2646
  if (error instanceof Error) {
1993
- console.error(chalk18.red(`
2647
+ console.error(chalk19.red(`
1994
2648
  \u2717 ${error.message}
1995
2649
  `));
1996
2650
  }
@@ -2002,7 +2656,7 @@ config.command("set <key> <value>").description("Set a configuration value").act
2002
2656
  await configSetCommand(key, value);
2003
2657
  } catch (error) {
2004
2658
  if (error instanceof Error) {
2005
- console.error(chalk18.red(`
2659
+ console.error(chalk19.red(`
2006
2660
  \u2717 ${error.message}
2007
2661
  `));
2008
2662
  }
@@ -2014,7 +2668,7 @@ config.command("list").description("List all configuration values").action(async
2014
2668
  await configListCommand();
2015
2669
  } catch (error) {
2016
2670
  if (error instanceof Error) {
2017
- console.error(chalk18.red(`
2671
+ console.error(chalk19.red(`
2018
2672
  \u2717 ${error.message}
2019
2673
  `));
2020
2674
  }
@@ -2026,7 +2680,7 @@ program.command("list").description("List all replicas").option("-p, --page <pag
2026
2680
  await replicaListCommand(options);
2027
2681
  } catch (error) {
2028
2682
  if (error instanceof Error) {
2029
- console.error(chalk18.red(`
2683
+ console.error(chalk19.red(`
2030
2684
  \u2717 ${error.message}
2031
2685
  `));
2032
2686
  }
@@ -2038,7 +2692,7 @@ program.command("get <id>").description("Get replica details by ID").action(asyn
2038
2692
  await replicaGetCommand(id);
2039
2693
  } catch (error) {
2040
2694
  if (error instanceof Error) {
2041
- console.error(chalk18.red(`
2695
+ console.error(chalk19.red(`
2042
2696
  \u2717 ${error.message}
2043
2697
  `));
2044
2698
  }
@@ -2050,7 +2704,7 @@ program.command("create [name]").description("Create a new replica").option("-m,
2050
2704
  await replicaCreateCommand(name, options);
2051
2705
  } catch (error) {
2052
2706
  if (error instanceof Error) {
2053
- console.error(chalk18.red(`
2707
+ console.error(chalk19.red(`
2054
2708
  \u2717 ${error.message}
2055
2709
  `));
2056
2710
  }
@@ -2062,7 +2716,7 @@ program.command("send <id>").description("Send a message to a replica").option("
2062
2716
  await replicaSendCommand(id, options);
2063
2717
  } catch (error) {
2064
2718
  if (error instanceof Error) {
2065
- console.error(chalk18.red(`
2719
+ console.error(chalk19.red(`
2066
2720
  \u2717 ${error.message}
2067
2721
  `));
2068
2722
  }
@@ -2074,7 +2728,7 @@ program.command("delete <id>").description("Delete a replica").option("-f, --for
2074
2728
  await replicaDeleteCommand(id, options);
2075
2729
  } catch (error) {
2076
2730
  if (error instanceof Error) {
2077
- console.error(chalk18.red(`
2731
+ console.error(chalk19.red(`
2078
2732
  \u2717 ${error.message}
2079
2733
  `));
2080
2734
  }
@@ -2086,7 +2740,95 @@ program.command("read <id>").description("Read conversation history of a replica
2086
2740
  await replicaReadCommand(id, options);
2087
2741
  } catch (error) {
2088
2742
  if (error instanceof Error) {
2089
- console.error(chalk18.red(`
2743
+ console.error(chalk19.red(`
2744
+ \u2717 ${error.message}
2745
+ `));
2746
+ }
2747
+ process.exit(1);
2748
+ }
2749
+ });
2750
+ var automation = program.command("automation").alias("auto").description("Manage automations");
2751
+ automation.command("list").description("List all automations").option("-p, --page <page>", "Page number").option("-l, --limit <limit>", "Number of items per page").action(async (options) => {
2752
+ try {
2753
+ await automationListCommand(options);
2754
+ } catch (error) {
2755
+ if (error instanceof Error) {
2756
+ console.error(chalk19.red(`
2757
+ \u2717 ${error.message}
2758
+ `));
2759
+ }
2760
+ process.exit(1);
2761
+ }
2762
+ });
2763
+ automation.command("get <id>").description("Get automation details by ID").action(async (id) => {
2764
+ try {
2765
+ await automationGetCommand(id);
2766
+ } catch (error) {
2767
+ if (error instanceof Error) {
2768
+ console.error(chalk19.red(`
2769
+ \u2717 ${error.message}
2770
+ `));
2771
+ }
2772
+ process.exit(1);
2773
+ }
2774
+ });
2775
+ automation.command("create [name]").description("Create a new automation").option("-p, --prompt <prompt>", "Prompt for the automation").option("-r, --repositories <repositories>", "Comma-separated repository names").option("-s, --repository-set <name>", "Use a repository set (mutually exclusive with -r)").option("--trigger-cron <schedule>", 'Cron schedule expression (e.g. "0 9 * * 1-5")').option("--trigger-cron-timezone <timezone>", "Timezone for cron trigger (default: UTC)").option("--trigger-github <event>", 'GitHub event (e.g. "pull_request.opened")').option("--trigger-github-repos <repos>", "Comma-separated repo names to filter GitHub trigger").option("--lifecycle <policy>", "Workspace lifecycle: delete_when_done, delete_after_inactivity, default").option("--auto-stop-minutes <minutes>", "Inactivity timeout in minutes (3-1440, requires --lifecycle delete_after_inactivity)").option("--disabled", "Create in disabled state").action(async (name, options) => {
2776
+ try {
2777
+ await automationCreateCommand(name, {
2778
+ ...options,
2779
+ enabled: !options.disabled
2780
+ });
2781
+ } catch (error) {
2782
+ if (error instanceof Error) {
2783
+ console.error(chalk19.red(`
2784
+ \u2717 ${error.message}
2785
+ `));
2786
+ }
2787
+ process.exit(1);
2788
+ }
2789
+ });
2790
+ automation.command("edit <id>").description("Edit an existing automation").option("-n, --name <name>", "New name").option("-p, --prompt <prompt>", "New prompt").option("-e, --enabled <enabled>", "Enable or disable (true/false)").option("--trigger-cron <schedule>", "Set cron schedule (replaces existing triggers)").option("--trigger-cron-timezone <timezone>", "Timezone for cron trigger").option("--trigger-github <event>", "Set GitHub event (replaces existing triggers)").option("--trigger-github-repos <repos>", "Comma-separated repo names to filter GitHub trigger").option("-r, --repositories <repositories>", "Comma-separated repository names").option("-s, --repository-set <name>", "Use a repository set (mutually exclusive with -r)").option("--lifecycle <policy>", "Workspace lifecycle: delete_when_done, delete_after_inactivity, default").option("--auto-stop-minutes <minutes>", "Inactivity timeout in minutes (3-1440, requires --lifecycle delete_after_inactivity)").action(async (id, options) => {
2791
+ try {
2792
+ await automationEditCommand(id, options);
2793
+ } catch (error) {
2794
+ if (error instanceof Error) {
2795
+ console.error(chalk19.red(`
2796
+ \u2717 ${error.message}
2797
+ `));
2798
+ }
2799
+ process.exit(1);
2800
+ }
2801
+ });
2802
+ automation.command("run <id>").description("Manually trigger an automation (cron-triggered automations only)").action(async (id) => {
2803
+ try {
2804
+ await automationRunCommand(id);
2805
+ } catch (error) {
2806
+ if (error instanceof Error) {
2807
+ console.error(chalk19.red(`
2808
+ \u2717 ${error.message}
2809
+ `));
2810
+ }
2811
+ process.exit(1);
2812
+ }
2813
+ });
2814
+ automation.command("delete <id>").description("Delete an automation").option("-f, --force", "Skip confirmation prompt").action(async (id, options) => {
2815
+ try {
2816
+ await automationDeleteCommand(id, options);
2817
+ } catch (error) {
2818
+ if (error instanceof Error) {
2819
+ console.error(chalk19.red(`
2820
+ \u2717 ${error.message}
2821
+ `));
2822
+ }
2823
+ process.exit(1);
2824
+ }
2825
+ });
2826
+ automation.action(async () => {
2827
+ try {
2828
+ await automationListCommand({});
2829
+ } catch (error) {
2830
+ if (error instanceof Error) {
2831
+ console.error(chalk19.red(`
2090
2832
  \u2717 ${error.message}
2091
2833
  `));
2092
2834
  }
@@ -2099,7 +2841,7 @@ repos.command("list").description("List all repositories").action(async () => {
2099
2841
  await repositoriesListCommand();
2100
2842
  } catch (error) {
2101
2843
  if (error instanceof Error) {
2102
- console.error(chalk18.red(`
2844
+ console.error(chalk19.red(`
2103
2845
  \u2717 ${error.message}
2104
2846
  `));
2105
2847
  }
@@ -2111,7 +2853,7 @@ repos.action(async () => {
2111
2853
  await repositoriesListCommand();
2112
2854
  } catch (error) {
2113
2855
  if (error instanceof Error) {
2114
- console.error(chalk18.red(`
2856
+ console.error(chalk19.red(`
2115
2857
  \u2717 ${error.message}
2116
2858
  `));
2117
2859
  }
@@ -2123,7 +2865,7 @@ program.command("interact").alias("i").description("Launch the interactive termi
2123
2865
  await interactiveCommand();
2124
2866
  } catch (error) {
2125
2867
  if (error instanceof Error) {
2126
- console.error(chalk18.red(`
2868
+ console.error(chalk19.red(`
2127
2869
  \u2717 ${error.message}
2128
2870
  `));
2129
2871
  }
@@ -2164,7 +2906,7 @@ if (isAgentMode()) {
2164
2906
  await previewAddCommand(workspaceId, options);
2165
2907
  } catch (error) {
2166
2908
  if (error instanceof Error) {
2167
- console.error(chalk18.red(`
2909
+ console.error(chalk19.red(`
2168
2910
  \u2717 ${error.message}
2169
2911
  `));
2170
2912
  }
@@ -2176,7 +2918,7 @@ if (isAgentMode()) {
2176
2918
  await previewListCommand(workspaceId);
2177
2919
  } catch (error) {
2178
2920
  if (error instanceof Error) {
2179
- console.error(chalk18.red(`
2921
+ console.error(chalk19.red(`
2180
2922
  \u2717 ${error.message}
2181
2923
  `));
2182
2924
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-cli",
3
- "version": "0.2.60",
3
+ "version": "0.2.61",
4
4
  "description": "CLI for managing Replicas workspaces - SSH into cloud dev environments with automatic port forwarding",
5
5
  "main": "dist/index.mjs",
6
6
  "bin": {