flowcat 1.6.3 → 1.7.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/core/config.ts CHANGED
@@ -1,8 +1,10 @@
1
- import { access, mkdir } from "node:fs/promises";
1
+ import { access, mkdir, readFile } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import path from "node:path";
4
4
 
5
- import { APP_NAME } from "./constants";
5
+ import { parse as parseEnv } from "dotenv";
6
+
7
+ import { APP_NAME, ENV_FILE } from "./constants";
6
8
  import { readJsonFile, writeJsonAtomic } from "./json";
7
9
 
8
10
  export type PluginEntry = {
@@ -91,7 +93,33 @@ export const resolveAutoCommitEnabled = async (workspaceRoot: string): Promise<b
91
93
  return globalConfig.autoCommit ?? false;
92
94
  };
93
95
 
96
+ export const resolveWorkspaceEnvPath = (workspaceRoot: string): string => {
97
+ return path.join(workspaceRoot, ENV_FILE);
98
+ };
99
+
100
+ export const resolveGlobalEnvPath = (): string => {
101
+ const configHome = process.env.XDG_CONFIG_HOME ?? path.join(homedir(), ".config");
102
+ return path.join(configHome, APP_NAME, ENV_FILE);
103
+ };
104
+
105
+ /**
106
+ * Read and parse an env file, returning empty object if file doesn't exist
107
+ */
108
+ export const readEnvFile = async (filePath: string): Promise<Record<string, string>> => {
109
+ if (!(await configFileExists(filePath))) {
110
+ return {};
111
+ }
112
+
113
+ try {
114
+ const content = await readFile(filePath, "utf8");
115
+ return parseEnv(content);
116
+ } catch {
117
+ return {};
118
+ }
119
+ };
120
+
94
121
  export const resolveGithubToken = async (workspaceRoot: string): Promise<string | null> => {
122
+ // 1. Check environment variables first
95
123
  if (process.env.FLOWCAT_GITHUB_TOKEN) {
96
124
  return process.env.FLOWCAT_GITHUB_TOKEN;
97
125
  }
@@ -100,6 +128,19 @@ export const resolveGithubToken = async (workspaceRoot: string): Promise<string
100
128
  return process.env.IL_GITHUB_TOKEN;
101
129
  }
102
130
 
131
+ // 2. Check workspace .env file
132
+ const workspaceEnv = await readEnvFile(resolveWorkspaceEnvPath(workspaceRoot));
133
+ if (workspaceEnv.GITHUB_TOKEN) {
134
+ return workspaceEnv.GITHUB_TOKEN;
135
+ }
136
+
137
+ // 3. Check global .env file
138
+ const globalEnv = await readEnvFile(resolveGlobalEnvPath());
139
+ if (globalEnv.GITHUB_TOKEN) {
140
+ return globalEnv.GITHUB_TOKEN;
141
+ }
142
+
143
+ // 4. Fallback to config.json (for backwards compatibility)
103
144
  const workspaceConfig = await readConfigFile(resolveWorkspaceConfigPath(workspaceRoot));
104
145
  if (workspaceConfig.github?.token) {
105
146
  return workspaceConfig.github.token;
package/core/constants.ts CHANGED
@@ -4,5 +4,6 @@ export const LOCK_DIR = ".lock";
4
4
  export const LOCK_FILE = "store.lock";
5
5
  export const TASKS_DIR = "tasks";
6
6
  export const PLUGINS_DIR = "plugins";
7
+ export const ENV_FILE = ".env";
7
8
 
8
9
  export const STATUS_ORDER = ["backlog", "active", "paused", "completed", "cancelled"] as const;
package/core/gitignore.ts CHANGED
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
 
4
4
  import { APP_DIR } from "./constants";
5
5
 
6
- const ignoredEntries = [`${APP_DIR}/.lock/`, `${APP_DIR}/config.json`];
6
+ const ignoredEntries = [`${APP_DIR}/.lock/`];
7
7
 
8
8
  export const ensureWorkspaceIgnored = async (repoRoot: string): Promise<boolean> => {
9
9
  const gitignorePath = path.join(repoRoot, ".gitignore");
package/core/workspace.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { access, mkdir } from "node:fs/promises";
1
+ import { access, mkdir, writeFile } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import path from "node:path";
4
4
 
5
5
  import { configFileExists, resolveGlobalConfigPath, resolveWorkspaceConfigPath } from "./config";
6
- import { APP_DIR, APP_NAME, LOCK_DIR, PLUGINS_DIR, STATUS_ORDER, TASKS_DIR } from "./constants";
6
+ import { APP_DIR, APP_NAME, ENV_FILE, LOCK_DIR, PLUGINS_DIR, STATUS_ORDER, TASKS_DIR } from "./constants";
7
7
  import { writeJsonAtomic } from "./json";
8
8
 
9
9
  export type WorkspaceKind = "explicit" | "global" | "repo";
@@ -106,22 +106,24 @@ export const ensureWorkspaceLayout = async (workspaceRoot: string): Promise<void
106
106
  ),
107
107
  );
108
108
 
109
- // Create plugins/package.json if it doesn't exist
110
- await ensurePluginsPackageJson(workspaceRoot);
109
+ // Create package.json for TypeScript plugin development
110
+ await ensureWorkspacePackageJson(workspaceRoot);
111
+ // Create .gitignore for workspace-local files
112
+ await ensureWorkspaceGitignore(workspaceRoot);
111
113
  };
112
114
 
113
115
  /**
114
- * Ensure plugins directory has a package.json for TypeScript plugin development
116
+ * Ensure workspace has a package.json for TypeScript plugin development
115
117
  */
116
- export const ensurePluginsPackageJson = async (workspaceRoot: string): Promise<void> => {
117
- const packageJsonPath = path.join(workspaceRoot, PLUGINS_DIR, "package.json");
118
+ export const ensureWorkspacePackageJson = async (workspaceRoot: string): Promise<void> => {
119
+ const packageJsonPath = path.join(workspaceRoot, "package.json");
118
120
 
119
121
  if (await exists(packageJsonPath)) {
120
122
  return;
121
123
  }
122
124
 
123
125
  const packageJson = {
124
- name: "flowcat-plugins",
126
+ name: "flowcat-workspace",
125
127
  type: "module",
126
128
  private: true,
127
129
  dependencies: {
@@ -131,3 +133,21 @@ export const ensurePluginsPackageJson = async (workspaceRoot: string): Promise<v
131
133
 
132
134
  await writeJsonAtomic(packageJsonPath, packageJson);
133
135
  };
136
+
137
+ /**
138
+ * Ensure workspace has a .gitignore for local files that shouldn't be committed
139
+ */
140
+ export const ensureWorkspaceGitignore = async (workspaceRoot: string): Promise<void> => {
141
+ const gitignorePath = path.join(workspaceRoot, ".gitignore");
142
+
143
+ if (await exists(gitignorePath)) {
144
+ return;
145
+ }
146
+
147
+ const gitignoreContent = `# Flowcat workspace local files
148
+ ${ENV_FILE}
149
+ ${LOCK_DIR}/
150
+ `;
151
+
152
+ await writeFile(gitignorePath, gitignoreContent, "utf8");
153
+ };
package/dist/fcat.mjs CHANGED
@@ -27642,6 +27642,401 @@ var init_chunk_fcedq94e = __esm(async () => {
27642
27642
  import_react_devtools_core.default.connectToDevTools();
27643
27643
  });
27644
27644
 
27645
+ // ../../node_modules/.bun/dotenv@17.2.3/node_modules/dotenv/package.json
27646
+ var require_package = __commonJS((exports, module2) => {
27647
+ module2.exports = {
27648
+ name: "dotenv",
27649
+ version: "17.2.3",
27650
+ description: "Loads environment variables from .env file",
27651
+ main: "lib/main.js",
27652
+ types: "lib/main.d.ts",
27653
+ exports: {
27654
+ ".": {
27655
+ types: "./lib/main.d.ts",
27656
+ require: "./lib/main.js",
27657
+ default: "./lib/main.js"
27658
+ },
27659
+ "./config": "./config.js",
27660
+ "./config.js": "./config.js",
27661
+ "./lib/env-options": "./lib/env-options.js",
27662
+ "./lib/env-options.js": "./lib/env-options.js",
27663
+ "./lib/cli-options": "./lib/cli-options.js",
27664
+ "./lib/cli-options.js": "./lib/cli-options.js",
27665
+ "./package.json": "./package.json"
27666
+ },
27667
+ scripts: {
27668
+ "dts-check": "tsc --project tests/types/tsconfig.json",
27669
+ lint: "standard",
27670
+ pretest: "npm run lint && npm run dts-check",
27671
+ test: "tap run tests/**/*.js --allow-empty-coverage --disable-coverage --timeout=60000",
27672
+ "test:coverage": "tap run tests/**/*.js --show-full-coverage --timeout=60000 --coverage-report=text --coverage-report=lcov",
27673
+ prerelease: "npm test",
27674
+ release: "standard-version"
27675
+ },
27676
+ repository: {
27677
+ type: "git",
27678
+ url: "git://github.com/motdotla/dotenv.git"
27679
+ },
27680
+ homepage: "https://github.com/motdotla/dotenv#readme",
27681
+ funding: "https://dotenvx.com",
27682
+ keywords: [
27683
+ "dotenv",
27684
+ "env",
27685
+ ".env",
27686
+ "environment",
27687
+ "variables",
27688
+ "config",
27689
+ "settings"
27690
+ ],
27691
+ readmeFilename: "README.md",
27692
+ license: "BSD-2-Clause",
27693
+ devDependencies: {
27694
+ "@types/node": "^18.11.3",
27695
+ decache: "^4.6.2",
27696
+ sinon: "^14.0.1",
27697
+ standard: "^17.0.0",
27698
+ "standard-version": "^9.5.0",
27699
+ tap: "^19.2.0",
27700
+ typescript: "^4.8.4"
27701
+ },
27702
+ engines: {
27703
+ node: ">=12"
27704
+ },
27705
+ browser: {
27706
+ fs: false
27707
+ }
27708
+ };
27709
+ });
27710
+
27711
+ // ../../node_modules/.bun/dotenv@17.2.3/node_modules/dotenv/lib/main.js
27712
+ var require_main = __commonJS((exports, module2) => {
27713
+ var fs2 = __require("fs");
27714
+ var path2 = __require("path");
27715
+ var os2 = __require("os");
27716
+ var crypto = __require("crypto");
27717
+ var packageJson = require_package();
27718
+ var version = packageJson.version;
27719
+ var TIPS = [
27720
+ "\uD83D\uDD10 encrypt with Dotenvx: https://dotenvx.com",
27721
+ "\uD83D\uDD10 prevent committing .env to code: https://dotenvx.com/precommit",
27722
+ "\uD83D\uDD10 prevent building .env in docker: https://dotenvx.com/prebuild",
27723
+ "\uD83D\uDCE1 add observability to secrets: https://dotenvx.com/ops",
27724
+ "\uD83D\uDC65 sync secrets across teammates & machines: https://dotenvx.com/ops",
27725
+ "\uD83D\uDDC2\uFE0F backup and recover secrets: https://dotenvx.com/ops",
27726
+ "\u2705 audit secrets and track compliance: https://dotenvx.com/ops",
27727
+ "\uD83D\uDD04 add secrets lifecycle management: https://dotenvx.com/ops",
27728
+ "\uD83D\uDD11 add access controls to secrets: https://dotenvx.com/ops",
27729
+ "\uD83D\uDEE0\uFE0F run anywhere with `dotenvx run -- yourcommand`",
27730
+ "\u2699\uFE0F specify custom .env file path with { path: '/custom/path/.env' }",
27731
+ "\u2699\uFE0F enable debug logging with { debug: true }",
27732
+ "\u2699\uFE0F override existing env vars with { override: true }",
27733
+ "\u2699\uFE0F suppress all logs with { quiet: true }",
27734
+ "\u2699\uFE0F write to custom object with { processEnv: myObject }",
27735
+ "\u2699\uFE0F load multiple .env files with { path: ['.env.local', '.env'] }"
27736
+ ];
27737
+ function _getRandomTip() {
27738
+ return TIPS[Math.floor(Math.random() * TIPS.length)];
27739
+ }
27740
+ function parseBoolean(value) {
27741
+ if (typeof value === "string") {
27742
+ return !["false", "0", "no", "off", ""].includes(value.toLowerCase());
27743
+ }
27744
+ return Boolean(value);
27745
+ }
27746
+ function supportsAnsi() {
27747
+ return process.stdout.isTTY;
27748
+ }
27749
+ function dim2(text) {
27750
+ return supportsAnsi() ? `\x1B[2m${text}\x1B[0m` : text;
27751
+ }
27752
+ var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
27753
+ function parse2(src) {
27754
+ const obj = {};
27755
+ let lines = src.toString();
27756
+ lines = lines.replace(/\r\n?/mg, `
27757
+ `);
27758
+ let match;
27759
+ while ((match = LINE.exec(lines)) != null) {
27760
+ const key = match[1];
27761
+ let value = match[2] || "";
27762
+ value = value.trim();
27763
+ const maybeQuote = value[0];
27764
+ value = value.replace(/^(['"`])([\s\S]*)\1$/mg, "$2");
27765
+ if (maybeQuote === '"') {
27766
+ value = value.replace(/\\n/g, `
27767
+ `);
27768
+ value = value.replace(/\\r/g, "\r");
27769
+ }
27770
+ obj[key] = value;
27771
+ }
27772
+ return obj;
27773
+ }
27774
+ function _parseVault(options) {
27775
+ options = options || {};
27776
+ const vaultPath = _vaultPath(options);
27777
+ options.path = vaultPath;
27778
+ const result = DotenvModule.configDotenv(options);
27779
+ if (!result.parsed) {
27780
+ const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
27781
+ err.code = "MISSING_DATA";
27782
+ throw err;
27783
+ }
27784
+ const keys = _dotenvKey(options).split(",");
27785
+ const length = keys.length;
27786
+ let decrypted;
27787
+ for (let i = 0;i < length; i++) {
27788
+ try {
27789
+ const key = keys[i].trim();
27790
+ const attrs = _instructions(result, key);
27791
+ decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
27792
+ break;
27793
+ } catch (error) {
27794
+ if (i + 1 >= length) {
27795
+ throw error;
27796
+ }
27797
+ }
27798
+ }
27799
+ return DotenvModule.parse(decrypted);
27800
+ }
27801
+ function _warn(message) {
27802
+ console.error(`[dotenv@${version}][WARN] ${message}`);
27803
+ }
27804
+ function _debug(message) {
27805
+ console.log(`[dotenv@${version}][DEBUG] ${message}`);
27806
+ }
27807
+ function _log(message) {
27808
+ console.log(`[dotenv@${version}] ${message}`);
27809
+ }
27810
+ function _dotenvKey(options) {
27811
+ if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) {
27812
+ return options.DOTENV_KEY;
27813
+ }
27814
+ if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
27815
+ return process.env.DOTENV_KEY;
27816
+ }
27817
+ return "";
27818
+ }
27819
+ function _instructions(result, dotenvKey) {
27820
+ let uri;
27821
+ try {
27822
+ uri = new URL(dotenvKey);
27823
+ } catch (error) {
27824
+ if (error.code === "ERR_INVALID_URL") {
27825
+ const err = new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development");
27826
+ err.code = "INVALID_DOTENV_KEY";
27827
+ throw err;
27828
+ }
27829
+ throw error;
27830
+ }
27831
+ const key = uri.password;
27832
+ if (!key) {
27833
+ const err = new Error("INVALID_DOTENV_KEY: Missing key part");
27834
+ err.code = "INVALID_DOTENV_KEY";
27835
+ throw err;
27836
+ }
27837
+ const environment = uri.searchParams.get("environment");
27838
+ if (!environment) {
27839
+ const err = new Error("INVALID_DOTENV_KEY: Missing environment part");
27840
+ err.code = "INVALID_DOTENV_KEY";
27841
+ throw err;
27842
+ }
27843
+ const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
27844
+ const ciphertext = result.parsed[environmentKey];
27845
+ if (!ciphertext) {
27846
+ const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
27847
+ err.code = "NOT_FOUND_DOTENV_ENVIRONMENT";
27848
+ throw err;
27849
+ }
27850
+ return { ciphertext, key };
27851
+ }
27852
+ function _vaultPath(options) {
27853
+ let possibleVaultPath = null;
27854
+ if (options && options.path && options.path.length > 0) {
27855
+ if (Array.isArray(options.path)) {
27856
+ for (const filepath of options.path) {
27857
+ if (fs2.existsSync(filepath)) {
27858
+ possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
27859
+ }
27860
+ }
27861
+ } else {
27862
+ possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
27863
+ }
27864
+ } else {
27865
+ possibleVaultPath = path2.resolve(process.cwd(), ".env.vault");
27866
+ }
27867
+ if (fs2.existsSync(possibleVaultPath)) {
27868
+ return possibleVaultPath;
27869
+ }
27870
+ return null;
27871
+ }
27872
+ function _resolveHome(envPath) {
27873
+ return envPath[0] === "~" ? path2.join(os2.homedir(), envPath.slice(1)) : envPath;
27874
+ }
27875
+ function _configVault(options) {
27876
+ const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || options && options.debug);
27877
+ const quiet = parseBoolean(process.env.DOTENV_CONFIG_QUIET || options && options.quiet);
27878
+ if (debug || !quiet) {
27879
+ _log("Loading env from encrypted .env.vault");
27880
+ }
27881
+ const parsed = DotenvModule._parseVault(options);
27882
+ let processEnv = process.env;
27883
+ if (options && options.processEnv != null) {
27884
+ processEnv = options.processEnv;
27885
+ }
27886
+ DotenvModule.populate(processEnv, parsed, options);
27887
+ return { parsed };
27888
+ }
27889
+ function configDotenv(options) {
27890
+ const dotenvPath = path2.resolve(process.cwd(), ".env");
27891
+ let encoding = "utf8";
27892
+ let processEnv = process.env;
27893
+ if (options && options.processEnv != null) {
27894
+ processEnv = options.processEnv;
27895
+ }
27896
+ let debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || options && options.debug);
27897
+ let quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || options && options.quiet);
27898
+ if (options && options.encoding) {
27899
+ encoding = options.encoding;
27900
+ } else {
27901
+ if (debug) {
27902
+ _debug("No encoding is specified. UTF-8 is used by default");
27903
+ }
27904
+ }
27905
+ let optionPaths = [dotenvPath];
27906
+ if (options && options.path) {
27907
+ if (!Array.isArray(options.path)) {
27908
+ optionPaths = [_resolveHome(options.path)];
27909
+ } else {
27910
+ optionPaths = [];
27911
+ for (const filepath of options.path) {
27912
+ optionPaths.push(_resolveHome(filepath));
27913
+ }
27914
+ }
27915
+ }
27916
+ let lastError;
27917
+ const parsedAll = {};
27918
+ for (const path3 of optionPaths) {
27919
+ try {
27920
+ const parsed = DotenvModule.parse(fs2.readFileSync(path3, { encoding }));
27921
+ DotenvModule.populate(parsedAll, parsed, options);
27922
+ } catch (e) {
27923
+ if (debug) {
27924
+ _debug(`Failed to load ${path3} ${e.message}`);
27925
+ }
27926
+ lastError = e;
27927
+ }
27928
+ }
27929
+ const populated = DotenvModule.populate(processEnv, parsedAll, options);
27930
+ debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || debug);
27931
+ quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || quiet);
27932
+ if (debug || !quiet) {
27933
+ const keysCount = Object.keys(populated).length;
27934
+ const shortPaths = [];
27935
+ for (const filePath of optionPaths) {
27936
+ try {
27937
+ const relative = path2.relative(process.cwd(), filePath);
27938
+ shortPaths.push(relative);
27939
+ } catch (e) {
27940
+ if (debug) {
27941
+ _debug(`Failed to load ${filePath} ${e.message}`);
27942
+ }
27943
+ lastError = e;
27944
+ }
27945
+ }
27946
+ _log(`injecting env (${keysCount}) from ${shortPaths.join(",")} ${dim2(`-- tip: ${_getRandomTip()}`)}`);
27947
+ }
27948
+ if (lastError) {
27949
+ return { parsed: parsedAll, error: lastError };
27950
+ } else {
27951
+ return { parsed: parsedAll };
27952
+ }
27953
+ }
27954
+ function config(options) {
27955
+ if (_dotenvKey(options).length === 0) {
27956
+ return DotenvModule.configDotenv(options);
27957
+ }
27958
+ const vaultPath = _vaultPath(options);
27959
+ if (!vaultPath) {
27960
+ _warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`);
27961
+ return DotenvModule.configDotenv(options);
27962
+ }
27963
+ return DotenvModule._configVault(options);
27964
+ }
27965
+ function decrypt(encrypted, keyStr) {
27966
+ const key = Buffer.from(keyStr.slice(-64), "hex");
27967
+ let ciphertext = Buffer.from(encrypted, "base64");
27968
+ const nonce = ciphertext.subarray(0, 12);
27969
+ const authTag = ciphertext.subarray(-16);
27970
+ ciphertext = ciphertext.subarray(12, -16);
27971
+ try {
27972
+ const aesgcm = crypto.createDecipheriv("aes-256-gcm", key, nonce);
27973
+ aesgcm.setAuthTag(authTag);
27974
+ return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
27975
+ } catch (error) {
27976
+ const isRange = error instanceof RangeError;
27977
+ const invalidKeyLength = error.message === "Invalid key length";
27978
+ const decryptionFailed = error.message === "Unsupported state or unable to authenticate data";
27979
+ if (isRange || invalidKeyLength) {
27980
+ const err = new Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");
27981
+ err.code = "INVALID_DOTENV_KEY";
27982
+ throw err;
27983
+ } else if (decryptionFailed) {
27984
+ const err = new Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");
27985
+ err.code = "DECRYPTION_FAILED";
27986
+ throw err;
27987
+ } else {
27988
+ throw error;
27989
+ }
27990
+ }
27991
+ }
27992
+ function populate(processEnv, parsed, options = {}) {
27993
+ const debug = Boolean(options && options.debug);
27994
+ const override = Boolean(options && options.override);
27995
+ const populated = {};
27996
+ if (typeof parsed !== "object") {
27997
+ const err = new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");
27998
+ err.code = "OBJECT_REQUIRED";
27999
+ throw err;
28000
+ }
28001
+ for (const key of Object.keys(parsed)) {
28002
+ if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
28003
+ if (override === true) {
28004
+ processEnv[key] = parsed[key];
28005
+ populated[key] = parsed[key];
28006
+ }
28007
+ if (debug) {
28008
+ if (override === true) {
28009
+ _debug(`"${key}" is already defined and WAS overwritten`);
28010
+ } else {
28011
+ _debug(`"${key}" is already defined and was NOT overwritten`);
28012
+ }
28013
+ }
28014
+ } else {
28015
+ processEnv[key] = parsed[key];
28016
+ populated[key] = parsed[key];
28017
+ }
28018
+ }
28019
+ return populated;
28020
+ }
28021
+ var DotenvModule = {
28022
+ configDotenv,
28023
+ _configVault,
28024
+ _parseVault,
28025
+ config,
28026
+ decrypt,
28027
+ parse: parse2,
28028
+ populate
28029
+ };
28030
+ exports.configDotenv = DotenvModule.configDotenv;
28031
+ exports._configVault = DotenvModule._configVault;
28032
+ exports._parseVault = DotenvModule._parseVault;
28033
+ exports.config = DotenvModule.config;
28034
+ exports.decrypt = DotenvModule.decrypt;
28035
+ exports.parse = DotenvModule.parse;
28036
+ exports.populate = DotenvModule.populate;
28037
+ module2.exports = DotenvModule;
28038
+ });
28039
+
27645
28040
  // ../../node_modules/.bun/imurmurhash@0.1.4/node_modules/imurmurhash/imurmurhash.js
27646
28041
  var require_imurmurhash = __commonJS((exports, module2) => {
27647
28042
  (function() {
@@ -28670,8 +29065,8 @@ GFS4: `);
28670
29065
  fs3.createReadStream = createReadStream;
28671
29066
  fs3.createWriteStream = createWriteStream;
28672
29067
  var fs$readFile = fs3.readFile;
28673
- fs3.readFile = readFile2;
28674
- function readFile2(path3, options, cb) {
29068
+ fs3.readFile = readFile3;
29069
+ function readFile3(path3, options, cb) {
28675
29070
  if (typeof options === "function")
28676
29071
  cb = options, options = null;
28677
29072
  return go$readFile(path3, options, cb);
@@ -72268,7 +72663,8 @@ var coerce = {
72268
72663
  };
72269
72664
  var NEVER = INVALID;
72270
72665
  // ../../packages/core/src/config.ts
72271
- import { access, mkdir } from "fs/promises";
72666
+ var import_dotenv = __toESM(require_main(), 1);
72667
+ import { access, mkdir, readFile as readFile2 } from "fs/promises";
72272
72668
  import { homedir } from "os";
72273
72669
  import path2 from "path";
72274
72670
 
@@ -72279,6 +72675,7 @@ var LOCK_DIR = ".lock";
72279
72675
  var LOCK_FILE = "store.lock";
72280
72676
  var TASKS_DIR = "tasks";
72281
72677
  var PLUGINS_DIR = "plugins";
72678
+ var ENV_FILE = ".env";
72282
72679
  var STATUS_ORDER = ["backlog", "active", "paused", "completed", "cancelled"];
72283
72680
 
72284
72681
  // ../../packages/core/src/json.ts
@@ -72326,6 +72723,24 @@ var updateConfigFile = async (filePath, updater) => {
72326
72723
  await writeConfigFile(filePath, next);
72327
72724
  return next;
72328
72725
  };
72726
+ var resolveWorkspaceEnvPath = (workspaceRoot) => {
72727
+ return path2.join(workspaceRoot, ENV_FILE);
72728
+ };
72729
+ var resolveGlobalEnvPath = () => {
72730
+ const configHome = process.env.XDG_CONFIG_HOME ?? path2.join(homedir(), ".config");
72731
+ return path2.join(configHome, APP_NAME, ENV_FILE);
72732
+ };
72733
+ var readEnvFile = async (filePath) => {
72734
+ if (!await configFileExists(filePath)) {
72735
+ return {};
72736
+ }
72737
+ try {
72738
+ const content = await readFile2(filePath, "utf8");
72739
+ return import_dotenv.parse(content);
72740
+ } catch {
72741
+ return {};
72742
+ }
72743
+ };
72329
72744
  var resolveGithubToken = async (workspaceRoot) => {
72330
72745
  if (process.env.FLOWCAT_GITHUB_TOKEN) {
72331
72746
  return process.env.FLOWCAT_GITHUB_TOKEN;
@@ -72333,6 +72748,14 @@ var resolveGithubToken = async (workspaceRoot) => {
72333
72748
  if (process.env.IL_GITHUB_TOKEN) {
72334
72749
  return process.env.IL_GITHUB_TOKEN;
72335
72750
  }
72751
+ const workspaceEnv = await readEnvFile(resolveWorkspaceEnvPath(workspaceRoot));
72752
+ if (workspaceEnv.GITHUB_TOKEN) {
72753
+ return workspaceEnv.GITHUB_TOKEN;
72754
+ }
72755
+ const globalEnv = await readEnvFile(resolveGlobalEnvPath());
72756
+ if (globalEnv.GITHUB_TOKEN) {
72757
+ return globalEnv.GITHUB_TOKEN;
72758
+ }
72336
72759
  const workspaceConfig = await readConfigFile(resolveWorkspaceConfigPath(workspaceRoot));
72337
72760
  if (workspaceConfig.github?.token) {
72338
72761
  return workspaceConfig.github.token;
@@ -72343,11 +72766,11 @@ var resolveGithubToken = async (workspaceRoot) => {
72343
72766
 
72344
72767
  // ../../packages/core/src/lock.ts
72345
72768
  var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
72346
- import { writeFile } from "fs/promises";
72769
+ import { writeFile as writeFile2 } from "fs/promises";
72347
72770
  import path5 from "path";
72348
72771
 
72349
72772
  // ../../packages/core/src/workspace.ts
72350
- import { access as access2, mkdir as mkdir2 } from "fs/promises";
72773
+ import { access as access2, mkdir as mkdir2, writeFile } from "fs/promises";
72351
72774
  import { homedir as homedir2 } from "os";
72352
72775
  import path3 from "path";
72353
72776
  var exists = async (filePath) => {
@@ -72412,15 +72835,16 @@ var ensureWorkspaceLayout = async (workspaceRoot) => {
72412
72835
  await mkdir2(path3.join(workspaceRoot, LOCK_DIR), { recursive: true });
72413
72836
  await mkdir2(path3.join(workspaceRoot, PLUGINS_DIR), { recursive: true });
72414
72837
  await Promise.all(STATUS_ORDER.map((status) => mkdir2(path3.join(workspaceRoot, TASKS_DIR, status), { recursive: true })));
72415
- await ensurePluginsPackageJson(workspaceRoot);
72838
+ await ensureWorkspacePackageJson(workspaceRoot);
72839
+ await ensureWorkspaceGitignore(workspaceRoot);
72416
72840
  };
72417
- var ensurePluginsPackageJson = async (workspaceRoot) => {
72418
- const packageJsonPath = path3.join(workspaceRoot, PLUGINS_DIR, "package.json");
72841
+ var ensureWorkspacePackageJson = async (workspaceRoot) => {
72842
+ const packageJsonPath = path3.join(workspaceRoot, "package.json");
72419
72843
  if (await exists(packageJsonPath)) {
72420
72844
  return;
72421
72845
  }
72422
72846
  const packageJson = {
72423
- name: "flowcat-plugins",
72847
+ name: "flowcat-workspace",
72424
72848
  type: "module",
72425
72849
  private: true,
72426
72850
  dependencies: {
@@ -72429,12 +72853,23 @@ var ensurePluginsPackageJson = async (workspaceRoot) => {
72429
72853
  };
72430
72854
  await writeJsonAtomic(packageJsonPath, packageJson);
72431
72855
  };
72856
+ var ensureWorkspaceGitignore = async (workspaceRoot) => {
72857
+ const gitignorePath = path3.join(workspaceRoot, ".gitignore");
72858
+ if (await exists(gitignorePath)) {
72859
+ return;
72860
+ }
72861
+ const gitignoreContent = `# Flowcat workspace local files
72862
+ ${ENV_FILE}
72863
+ ${LOCK_DIR}/
72864
+ `;
72865
+ await writeFile(gitignorePath, gitignoreContent, "utf8");
72866
+ };
72432
72867
 
72433
72868
  // ../../packages/core/src/lock.ts
72434
72869
  var withWorkspaceLock = async (workspaceRoot, fn) => {
72435
72870
  await ensureWorkspaceLayout(workspaceRoot);
72436
72871
  const lockPath = path5.join(workspaceRoot, LOCK_DIR, LOCK_FILE);
72437
- await writeFile(lockPath, "", { flag: "a" });
72872
+ await writeFile2(lockPath, "", { flag: "a" });
72438
72873
  const release = await import_proper_lockfile.default.lock(lockPath, {
72439
72874
  stale: 60000,
72440
72875
  retries: {
@@ -81750,14 +82185,14 @@ var taskMatchesQuery = (task, query) => {
81750
82185
  import path11 from "path";
81751
82186
 
81752
82187
  // ../../packages/core/src/gitignore.ts
81753
- import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
82188
+ import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
81754
82189
  import path10 from "path";
81755
- var ignoredEntries = [`${APP_DIR}/.lock/`, `${APP_DIR}/config.json`];
82190
+ var ignoredEntries = [`${APP_DIR}/.lock/`];
81756
82191
  var ensureWorkspaceIgnored = async (repoRoot) => {
81757
82192
  const gitignorePath = path10.join(repoRoot, ".gitignore");
81758
82193
  let current = "";
81759
82194
  try {
81760
- current = await readFile2(gitignorePath, "utf8");
82195
+ current = await readFile3(gitignorePath, "utf8");
81761
82196
  } catch {
81762
82197
  current = "";
81763
82198
  }
@@ -81773,7 +82208,7 @@ var ensureWorkspaceIgnored = async (repoRoot) => {
81773
82208
  const next = `${current}${separator}${missingEntries.join(`
81774
82209
  `)}
81775
82210
  `;
81776
- await writeFile2(gitignorePath, next, "utf8");
82211
+ await writeFile3(gitignorePath, next, "utf8");
81777
82212
  return true;
81778
82213
  };
81779
82214
 
package/dist/fweb ADDED
Binary file
package/dist/index.mjs CHANGED
@@ -1804,6 +1804,401 @@ var require_commander = __commonJS((exports, module) => {
1804
1804
  exports.InvalidOptionArgumentError = InvalidArgumentError;
1805
1805
  });
1806
1806
 
1807
+ // ../../node_modules/.bun/dotenv@17.2.3/node_modules/dotenv/package.json
1808
+ var require_package = __commonJS((exports, module) => {
1809
+ module.exports = {
1810
+ name: "dotenv",
1811
+ version: "17.2.3",
1812
+ description: "Loads environment variables from .env file",
1813
+ main: "lib/main.js",
1814
+ types: "lib/main.d.ts",
1815
+ exports: {
1816
+ ".": {
1817
+ types: "./lib/main.d.ts",
1818
+ require: "./lib/main.js",
1819
+ default: "./lib/main.js"
1820
+ },
1821
+ "./config": "./config.js",
1822
+ "./config.js": "./config.js",
1823
+ "./lib/env-options": "./lib/env-options.js",
1824
+ "./lib/env-options.js": "./lib/env-options.js",
1825
+ "./lib/cli-options": "./lib/cli-options.js",
1826
+ "./lib/cli-options.js": "./lib/cli-options.js",
1827
+ "./package.json": "./package.json"
1828
+ },
1829
+ scripts: {
1830
+ "dts-check": "tsc --project tests/types/tsconfig.json",
1831
+ lint: "standard",
1832
+ pretest: "npm run lint && npm run dts-check",
1833
+ test: "tap run tests/**/*.js --allow-empty-coverage --disable-coverage --timeout=60000",
1834
+ "test:coverage": "tap run tests/**/*.js --show-full-coverage --timeout=60000 --coverage-report=text --coverage-report=lcov",
1835
+ prerelease: "npm test",
1836
+ release: "standard-version"
1837
+ },
1838
+ repository: {
1839
+ type: "git",
1840
+ url: "git://github.com/motdotla/dotenv.git"
1841
+ },
1842
+ homepage: "https://github.com/motdotla/dotenv#readme",
1843
+ funding: "https://dotenvx.com",
1844
+ keywords: [
1845
+ "dotenv",
1846
+ "env",
1847
+ ".env",
1848
+ "environment",
1849
+ "variables",
1850
+ "config",
1851
+ "settings"
1852
+ ],
1853
+ readmeFilename: "README.md",
1854
+ license: "BSD-2-Clause",
1855
+ devDependencies: {
1856
+ "@types/node": "^18.11.3",
1857
+ decache: "^4.6.2",
1858
+ sinon: "^14.0.1",
1859
+ standard: "^17.0.0",
1860
+ "standard-version": "^9.5.0",
1861
+ tap: "^19.2.0",
1862
+ typescript: "^4.8.4"
1863
+ },
1864
+ engines: {
1865
+ node: ">=12"
1866
+ },
1867
+ browser: {
1868
+ fs: false
1869
+ }
1870
+ };
1871
+ });
1872
+
1873
+ // ../../node_modules/.bun/dotenv@17.2.3/node_modules/dotenv/lib/main.js
1874
+ var require_main = __commonJS((exports, module) => {
1875
+ var fs = __require("fs");
1876
+ var path = __require("path");
1877
+ var os = __require("os");
1878
+ var crypto = __require("crypto");
1879
+ var packageJson = require_package();
1880
+ var version = packageJson.version;
1881
+ var TIPS = [
1882
+ "\uD83D\uDD10 encrypt with Dotenvx: https://dotenvx.com",
1883
+ "\uD83D\uDD10 prevent committing .env to code: https://dotenvx.com/precommit",
1884
+ "\uD83D\uDD10 prevent building .env in docker: https://dotenvx.com/prebuild",
1885
+ "\uD83D\uDCE1 add observability to secrets: https://dotenvx.com/ops",
1886
+ "\uD83D\uDC65 sync secrets across teammates & machines: https://dotenvx.com/ops",
1887
+ "\uD83D\uDDC2️ backup and recover secrets: https://dotenvx.com/ops",
1888
+ "✅ audit secrets and track compliance: https://dotenvx.com/ops",
1889
+ "\uD83D\uDD04 add secrets lifecycle management: https://dotenvx.com/ops",
1890
+ "\uD83D\uDD11 add access controls to secrets: https://dotenvx.com/ops",
1891
+ "\uD83D\uDEE0️ run anywhere with `dotenvx run -- yourcommand`",
1892
+ "⚙️ specify custom .env file path with { path: '/custom/path/.env' }",
1893
+ "⚙️ enable debug logging with { debug: true }",
1894
+ "⚙️ override existing env vars with { override: true }",
1895
+ "⚙️ suppress all logs with { quiet: true }",
1896
+ "⚙️ write to custom object with { processEnv: myObject }",
1897
+ "⚙️ load multiple .env files with { path: ['.env.local', '.env'] }"
1898
+ ];
1899
+ function _getRandomTip() {
1900
+ return TIPS[Math.floor(Math.random() * TIPS.length)];
1901
+ }
1902
+ function parseBoolean(value) {
1903
+ if (typeof value === "string") {
1904
+ return !["false", "0", "no", "off", ""].includes(value.toLowerCase());
1905
+ }
1906
+ return Boolean(value);
1907
+ }
1908
+ function supportsAnsi() {
1909
+ return process.stdout.isTTY;
1910
+ }
1911
+ function dim(text) {
1912
+ return supportsAnsi() ? `\x1B[2m${text}\x1B[0m` : text;
1913
+ }
1914
+ var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
1915
+ function parse(src) {
1916
+ const obj = {};
1917
+ let lines = src.toString();
1918
+ lines = lines.replace(/\r\n?/mg, `
1919
+ `);
1920
+ let match;
1921
+ while ((match = LINE.exec(lines)) != null) {
1922
+ const key = match[1];
1923
+ let value = match[2] || "";
1924
+ value = value.trim();
1925
+ const maybeQuote = value[0];
1926
+ value = value.replace(/^(['"`])([\s\S]*)\1$/mg, "$2");
1927
+ if (maybeQuote === '"') {
1928
+ value = value.replace(/\\n/g, `
1929
+ `);
1930
+ value = value.replace(/\\r/g, "\r");
1931
+ }
1932
+ obj[key] = value;
1933
+ }
1934
+ return obj;
1935
+ }
1936
+ function _parseVault(options) {
1937
+ options = options || {};
1938
+ const vaultPath = _vaultPath(options);
1939
+ options.path = vaultPath;
1940
+ const result = DotenvModule.configDotenv(options);
1941
+ if (!result.parsed) {
1942
+ const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
1943
+ err.code = "MISSING_DATA";
1944
+ throw err;
1945
+ }
1946
+ const keys = _dotenvKey(options).split(",");
1947
+ const length = keys.length;
1948
+ let decrypted;
1949
+ for (let i = 0;i < length; i++) {
1950
+ try {
1951
+ const key = keys[i].trim();
1952
+ const attrs = _instructions(result, key);
1953
+ decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
1954
+ break;
1955
+ } catch (error) {
1956
+ if (i + 1 >= length) {
1957
+ throw error;
1958
+ }
1959
+ }
1960
+ }
1961
+ return DotenvModule.parse(decrypted);
1962
+ }
1963
+ function _warn(message) {
1964
+ console.error(`[dotenv@${version}][WARN] ${message}`);
1965
+ }
1966
+ function _debug(message) {
1967
+ console.log(`[dotenv@${version}][DEBUG] ${message}`);
1968
+ }
1969
+ function _log(message) {
1970
+ console.log(`[dotenv@${version}] ${message}`);
1971
+ }
1972
+ function _dotenvKey(options) {
1973
+ if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) {
1974
+ return options.DOTENV_KEY;
1975
+ }
1976
+ if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
1977
+ return process.env.DOTENV_KEY;
1978
+ }
1979
+ return "";
1980
+ }
1981
+ function _instructions(result, dotenvKey) {
1982
+ let uri;
1983
+ try {
1984
+ uri = new URL(dotenvKey);
1985
+ } catch (error) {
1986
+ if (error.code === "ERR_INVALID_URL") {
1987
+ const err = new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development");
1988
+ err.code = "INVALID_DOTENV_KEY";
1989
+ throw err;
1990
+ }
1991
+ throw error;
1992
+ }
1993
+ const key = uri.password;
1994
+ if (!key) {
1995
+ const err = new Error("INVALID_DOTENV_KEY: Missing key part");
1996
+ err.code = "INVALID_DOTENV_KEY";
1997
+ throw err;
1998
+ }
1999
+ const environment = uri.searchParams.get("environment");
2000
+ if (!environment) {
2001
+ const err = new Error("INVALID_DOTENV_KEY: Missing environment part");
2002
+ err.code = "INVALID_DOTENV_KEY";
2003
+ throw err;
2004
+ }
2005
+ const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
2006
+ const ciphertext = result.parsed[environmentKey];
2007
+ if (!ciphertext) {
2008
+ const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
2009
+ err.code = "NOT_FOUND_DOTENV_ENVIRONMENT";
2010
+ throw err;
2011
+ }
2012
+ return { ciphertext, key };
2013
+ }
2014
+ function _vaultPath(options) {
2015
+ let possibleVaultPath = null;
2016
+ if (options && options.path && options.path.length > 0) {
2017
+ if (Array.isArray(options.path)) {
2018
+ for (const filepath of options.path) {
2019
+ if (fs.existsSync(filepath)) {
2020
+ possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
2021
+ }
2022
+ }
2023
+ } else {
2024
+ possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
2025
+ }
2026
+ } else {
2027
+ possibleVaultPath = path.resolve(process.cwd(), ".env.vault");
2028
+ }
2029
+ if (fs.existsSync(possibleVaultPath)) {
2030
+ return possibleVaultPath;
2031
+ }
2032
+ return null;
2033
+ }
2034
+ function _resolveHome(envPath) {
2035
+ return envPath[0] === "~" ? path.join(os.homedir(), envPath.slice(1)) : envPath;
2036
+ }
2037
+ function _configVault(options) {
2038
+ const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || options && options.debug);
2039
+ const quiet = parseBoolean(process.env.DOTENV_CONFIG_QUIET || options && options.quiet);
2040
+ if (debug || !quiet) {
2041
+ _log("Loading env from encrypted .env.vault");
2042
+ }
2043
+ const parsed = DotenvModule._parseVault(options);
2044
+ let processEnv = process.env;
2045
+ if (options && options.processEnv != null) {
2046
+ processEnv = options.processEnv;
2047
+ }
2048
+ DotenvModule.populate(processEnv, parsed, options);
2049
+ return { parsed };
2050
+ }
2051
+ function configDotenv(options) {
2052
+ const dotenvPath = path.resolve(process.cwd(), ".env");
2053
+ let encoding = "utf8";
2054
+ let processEnv = process.env;
2055
+ if (options && options.processEnv != null) {
2056
+ processEnv = options.processEnv;
2057
+ }
2058
+ let debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || options && options.debug);
2059
+ let quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || options && options.quiet);
2060
+ if (options && options.encoding) {
2061
+ encoding = options.encoding;
2062
+ } else {
2063
+ if (debug) {
2064
+ _debug("No encoding is specified. UTF-8 is used by default");
2065
+ }
2066
+ }
2067
+ let optionPaths = [dotenvPath];
2068
+ if (options && options.path) {
2069
+ if (!Array.isArray(options.path)) {
2070
+ optionPaths = [_resolveHome(options.path)];
2071
+ } else {
2072
+ optionPaths = [];
2073
+ for (const filepath of options.path) {
2074
+ optionPaths.push(_resolveHome(filepath));
2075
+ }
2076
+ }
2077
+ }
2078
+ let lastError;
2079
+ const parsedAll = {};
2080
+ for (const path2 of optionPaths) {
2081
+ try {
2082
+ const parsed = DotenvModule.parse(fs.readFileSync(path2, { encoding }));
2083
+ DotenvModule.populate(parsedAll, parsed, options);
2084
+ } catch (e) {
2085
+ if (debug) {
2086
+ _debug(`Failed to load ${path2} ${e.message}`);
2087
+ }
2088
+ lastError = e;
2089
+ }
2090
+ }
2091
+ const populated = DotenvModule.populate(processEnv, parsedAll, options);
2092
+ debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || debug);
2093
+ quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || quiet);
2094
+ if (debug || !quiet) {
2095
+ const keysCount = Object.keys(populated).length;
2096
+ const shortPaths = [];
2097
+ for (const filePath of optionPaths) {
2098
+ try {
2099
+ const relative = path.relative(process.cwd(), filePath);
2100
+ shortPaths.push(relative);
2101
+ } catch (e) {
2102
+ if (debug) {
2103
+ _debug(`Failed to load ${filePath} ${e.message}`);
2104
+ }
2105
+ lastError = e;
2106
+ }
2107
+ }
2108
+ _log(`injecting env (${keysCount}) from ${shortPaths.join(",")} ${dim(`-- tip: ${_getRandomTip()}`)}`);
2109
+ }
2110
+ if (lastError) {
2111
+ return { parsed: parsedAll, error: lastError };
2112
+ } else {
2113
+ return { parsed: parsedAll };
2114
+ }
2115
+ }
2116
+ function config(options) {
2117
+ if (_dotenvKey(options).length === 0) {
2118
+ return DotenvModule.configDotenv(options);
2119
+ }
2120
+ const vaultPath = _vaultPath(options);
2121
+ if (!vaultPath) {
2122
+ _warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`);
2123
+ return DotenvModule.configDotenv(options);
2124
+ }
2125
+ return DotenvModule._configVault(options);
2126
+ }
2127
+ function decrypt(encrypted, keyStr) {
2128
+ const key = Buffer.from(keyStr.slice(-64), "hex");
2129
+ let ciphertext = Buffer.from(encrypted, "base64");
2130
+ const nonce = ciphertext.subarray(0, 12);
2131
+ const authTag = ciphertext.subarray(-16);
2132
+ ciphertext = ciphertext.subarray(12, -16);
2133
+ try {
2134
+ const aesgcm = crypto.createDecipheriv("aes-256-gcm", key, nonce);
2135
+ aesgcm.setAuthTag(authTag);
2136
+ return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
2137
+ } catch (error) {
2138
+ const isRange = error instanceof RangeError;
2139
+ const invalidKeyLength = error.message === "Invalid key length";
2140
+ const decryptionFailed = error.message === "Unsupported state or unable to authenticate data";
2141
+ if (isRange || invalidKeyLength) {
2142
+ const err = new Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");
2143
+ err.code = "INVALID_DOTENV_KEY";
2144
+ throw err;
2145
+ } else if (decryptionFailed) {
2146
+ const err = new Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");
2147
+ err.code = "DECRYPTION_FAILED";
2148
+ throw err;
2149
+ } else {
2150
+ throw error;
2151
+ }
2152
+ }
2153
+ }
2154
+ function populate(processEnv, parsed, options = {}) {
2155
+ const debug = Boolean(options && options.debug);
2156
+ const override = Boolean(options && options.override);
2157
+ const populated = {};
2158
+ if (typeof parsed !== "object") {
2159
+ const err = new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");
2160
+ err.code = "OBJECT_REQUIRED";
2161
+ throw err;
2162
+ }
2163
+ for (const key of Object.keys(parsed)) {
2164
+ if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
2165
+ if (override === true) {
2166
+ processEnv[key] = parsed[key];
2167
+ populated[key] = parsed[key];
2168
+ }
2169
+ if (debug) {
2170
+ if (override === true) {
2171
+ _debug(`"${key}" is already defined and WAS overwritten`);
2172
+ } else {
2173
+ _debug(`"${key}" is already defined and was NOT overwritten`);
2174
+ }
2175
+ }
2176
+ } else {
2177
+ processEnv[key] = parsed[key];
2178
+ populated[key] = parsed[key];
2179
+ }
2180
+ }
2181
+ return populated;
2182
+ }
2183
+ var DotenvModule = {
2184
+ configDotenv,
2185
+ _configVault,
2186
+ _parseVault,
2187
+ config,
2188
+ decrypt,
2189
+ parse,
2190
+ populate
2191
+ };
2192
+ exports.configDotenv = DotenvModule.configDotenv;
2193
+ exports._configVault = DotenvModule._configVault;
2194
+ exports._parseVault = DotenvModule._parseVault;
2195
+ exports.config = DotenvModule.config;
2196
+ exports.decrypt = DotenvModule.decrypt;
2197
+ exports.parse = DotenvModule.parse;
2198
+ exports.populate = DotenvModule.populate;
2199
+ module.exports = DotenvModule;
2200
+ });
2201
+
1807
2202
  // ../../node_modules/.bun/imurmurhash@0.1.4/node_modules/imurmurhash/imurmurhash.js
1808
2203
  var require_imurmurhash = __commonJS((exports, module) => {
1809
2204
  (function() {
@@ -2832,8 +3227,8 @@ GFS4: `);
2832
3227
  fs2.createReadStream = createReadStream;
2833
3228
  fs2.createWriteStream = createWriteStream;
2834
3229
  var fs$readFile = fs2.readFile;
2835
- fs2.readFile = readFile2;
2836
- function readFile2(path2, options, cb) {
3230
+ fs2.readFile = readFile3;
3231
+ function readFile3(path2, options, cb) {
2837
3232
  if (typeof options === "function")
2838
3233
  cb = options, options = null;
2839
3234
  return go$readFile(path2, options, cb);
@@ -25136,7 +25531,8 @@ var coerce = {
25136
25531
  };
25137
25532
  var NEVER = INVALID;
25138
25533
  // ../../packages/core/src/config.ts
25139
- import { access, mkdir } from "node:fs/promises";
25534
+ var import_dotenv = __toESM(require_main(), 1);
25535
+ import { access, mkdir, readFile as readFile2 } from "node:fs/promises";
25140
25536
  import { homedir } from "node:os";
25141
25537
  import path from "node:path";
25142
25538
 
@@ -25147,6 +25543,7 @@ var LOCK_DIR = ".lock";
25147
25543
  var LOCK_FILE = "store.lock";
25148
25544
  var TASKS_DIR = "tasks";
25149
25545
  var PLUGINS_DIR = "plugins";
25546
+ var ENV_FILE = ".env";
25150
25547
  var STATUS_ORDER = ["backlog", "active", "paused", "completed", "cancelled"];
25151
25548
 
25152
25549
  // ../../packages/core/src/json.ts
@@ -25202,6 +25599,24 @@ var resolveAutoCommitEnabled = async (workspaceRoot) => {
25202
25599
  const globalConfig = await readConfigFile(resolveGlobalConfigPath());
25203
25600
  return globalConfig.autoCommit ?? false;
25204
25601
  };
25602
+ var resolveWorkspaceEnvPath = (workspaceRoot) => {
25603
+ return path.join(workspaceRoot, ENV_FILE);
25604
+ };
25605
+ var resolveGlobalEnvPath = () => {
25606
+ const configHome = process.env.XDG_CONFIG_HOME ?? path.join(homedir(), ".config");
25607
+ return path.join(configHome, APP_NAME, ENV_FILE);
25608
+ };
25609
+ var readEnvFile = async (filePath) => {
25610
+ if (!await configFileExists(filePath)) {
25611
+ return {};
25612
+ }
25613
+ try {
25614
+ const content = await readFile2(filePath, "utf8");
25615
+ return import_dotenv.parse(content);
25616
+ } catch {
25617
+ return {};
25618
+ }
25619
+ };
25205
25620
  var resolveGithubToken = async (workspaceRoot) => {
25206
25621
  if (process.env.FLOWCAT_GITHUB_TOKEN) {
25207
25622
  return process.env.FLOWCAT_GITHUB_TOKEN;
@@ -25209,6 +25624,14 @@ var resolveGithubToken = async (workspaceRoot) => {
25209
25624
  if (process.env.IL_GITHUB_TOKEN) {
25210
25625
  return process.env.IL_GITHUB_TOKEN;
25211
25626
  }
25627
+ const workspaceEnv = await readEnvFile(resolveWorkspaceEnvPath(workspaceRoot));
25628
+ if (workspaceEnv.GITHUB_TOKEN) {
25629
+ return workspaceEnv.GITHUB_TOKEN;
25630
+ }
25631
+ const globalEnv = await readEnvFile(resolveGlobalEnvPath());
25632
+ if (globalEnv.GITHUB_TOKEN) {
25633
+ return globalEnv.GITHUB_TOKEN;
25634
+ }
25212
25635
  const workspaceConfig = await readConfigFile(resolveWorkspaceConfigPath(workspaceRoot));
25213
25636
  if (workspaceConfig.github?.token) {
25214
25637
  return workspaceConfig.github.token;
@@ -25219,11 +25642,11 @@ var resolveGithubToken = async (workspaceRoot) => {
25219
25642
 
25220
25643
  // ../../packages/core/src/lock.ts
25221
25644
  var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
25222
- import { writeFile } from "node:fs/promises";
25645
+ import { writeFile as writeFile2 } from "node:fs/promises";
25223
25646
  import path3 from "node:path";
25224
25647
 
25225
25648
  // ../../packages/core/src/workspace.ts
25226
- import { access as access2, mkdir as mkdir2 } from "node:fs/promises";
25649
+ import { access as access2, mkdir as mkdir2, writeFile } from "node:fs/promises";
25227
25650
  import { homedir as homedir2 } from "node:os";
25228
25651
  import path2 from "node:path";
25229
25652
  var exists = async (filePath) => {
@@ -25288,15 +25711,16 @@ var ensureWorkspaceLayout = async (workspaceRoot) => {
25288
25711
  await mkdir2(path2.join(workspaceRoot, LOCK_DIR), { recursive: true });
25289
25712
  await mkdir2(path2.join(workspaceRoot, PLUGINS_DIR), { recursive: true });
25290
25713
  await Promise.all(STATUS_ORDER.map((status) => mkdir2(path2.join(workspaceRoot, TASKS_DIR, status), { recursive: true })));
25291
- await ensurePluginsPackageJson(workspaceRoot);
25714
+ await ensureWorkspacePackageJson(workspaceRoot);
25715
+ await ensureWorkspaceGitignore(workspaceRoot);
25292
25716
  };
25293
- var ensurePluginsPackageJson = async (workspaceRoot) => {
25294
- const packageJsonPath = path2.join(workspaceRoot, PLUGINS_DIR, "package.json");
25717
+ var ensureWorkspacePackageJson = async (workspaceRoot) => {
25718
+ const packageJsonPath = path2.join(workspaceRoot, "package.json");
25295
25719
  if (await exists(packageJsonPath)) {
25296
25720
  return;
25297
25721
  }
25298
25722
  const packageJson = {
25299
- name: "flowcat-plugins",
25723
+ name: "flowcat-workspace",
25300
25724
  type: "module",
25301
25725
  private: true,
25302
25726
  dependencies: {
@@ -25305,12 +25729,23 @@ var ensurePluginsPackageJson = async (workspaceRoot) => {
25305
25729
  };
25306
25730
  await writeJsonAtomic(packageJsonPath, packageJson);
25307
25731
  };
25732
+ var ensureWorkspaceGitignore = async (workspaceRoot) => {
25733
+ const gitignorePath = path2.join(workspaceRoot, ".gitignore");
25734
+ if (await exists(gitignorePath)) {
25735
+ return;
25736
+ }
25737
+ const gitignoreContent = `# Flowcat workspace local files
25738
+ ${ENV_FILE}
25739
+ ${LOCK_DIR}/
25740
+ `;
25741
+ await writeFile(gitignorePath, gitignoreContent, "utf8");
25742
+ };
25308
25743
 
25309
25744
  // ../../packages/core/src/lock.ts
25310
25745
  var withWorkspaceLock = async (workspaceRoot, fn) => {
25311
25746
  await ensureWorkspaceLayout(workspaceRoot);
25312
25747
  const lockPath = path3.join(workspaceRoot, LOCK_DIR, LOCK_FILE);
25313
- await writeFile(lockPath, "", { flag: "a" });
25748
+ await writeFile2(lockPath, "", { flag: "a" });
25314
25749
  const release = await import_proper_lockfile.default.lock(lockPath, {
25315
25750
  stale: 60000,
25316
25751
  retries: {
@@ -30793,14 +31228,14 @@ var registerEditCommand = (program2, helpers) => {
30793
31228
  import path8 from "node:path";
30794
31229
 
30795
31230
  // ../../packages/core/src/gitignore.ts
30796
- import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
31231
+ import { readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
30797
31232
  import path7 from "node:path";
30798
- var ignoredEntries = [`${APP_DIR}/.lock/`, `${APP_DIR}/config.json`];
31233
+ var ignoredEntries = [`${APP_DIR}/.lock/`];
30799
31234
  var ensureWorkspaceIgnored = async (repoRoot) => {
30800
31235
  const gitignorePath = path7.join(repoRoot, ".gitignore");
30801
31236
  let current = "";
30802
31237
  try {
30803
- current = await readFile2(gitignorePath, "utf8");
31238
+ current = await readFile3(gitignorePath, "utf8");
30804
31239
  } catch {
30805
31240
  current = "";
30806
31241
  }
@@ -30816,7 +31251,7 @@ var ensureWorkspaceIgnored = async (repoRoot) => {
30816
31251
  const next = `${current}${separator}${missingEntries.join(`
30817
31252
  `)}
30818
31253
  `;
30819
- await writeFile2(gitignorePath, next, "utf8");
31254
+ await writeFile3(gitignorePath, next, "utf8");
30820
31255
  return true;
30821
31256
  };
30822
31257
 
@@ -31924,7 +32359,7 @@ var registerWhereCommand = (program2, helpers) => {
31924
32359
  };
31925
32360
 
31926
32361
  // helpers.ts
31927
- import { readFile as readFile3 } from "node:fs/promises";
32362
+ import { readFile as readFile4 } from "node:fs/promises";
31928
32363
  import path10 from "node:path";
31929
32364
  import { fileURLToPath } from "node:url";
31930
32365
 
@@ -32240,7 +32675,7 @@ var resolvePackageVersion = async () => {
32240
32675
  while (true) {
32241
32676
  const candidate = path10.join(current, "package.json");
32242
32677
  try {
32243
- const raw = await readFile3(candidate, "utf8");
32678
+ const raw = await readFile4(candidate, "utf8");
32244
32679
  const parsed = JSON.parse(raw);
32245
32680
  if (isPackageJson(parsed) && typeof parsed.version === "string") {
32246
32681
  return parsed.version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowcat",
3
- "version": "1.6.3",
3
+ "version": "1.7.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,7 +9,8 @@
9
9
  },
10
10
  "bin": {
11
11
  "flowcat": "dist/index.mjs",
12
- "fcat": "dist/fcat.mjs"
12
+ "fcat": "dist/fcat.mjs",
13
+ "fweb": "dist/fweb"
13
14
  },
14
15
  "files": [
15
16
  "dist",