flowcat 1.7.0 → 1.8.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/addTask.ts CHANGED
@@ -1,10 +1,8 @@
1
1
  import { z } from "zod";
2
2
 
3
- import { resolveGithubToken } from "./config";
4
3
  import { withWorkspaceLock } from "./lock";
5
4
  import { emitHook } from "./pluginManager";
6
5
  import { buildPrAttachment, fetchGitHubPr, parseGitHubPrUrl } from "./pr";
7
- import type { PrFetched } from "./schemas/pr";
8
6
  import type { Task } from "./schemas/task";
9
7
  import { taskSchema } from "./schemas/task";
10
8
  import { buildTask } from "./taskFactory";
@@ -59,15 +57,7 @@ export const addTask = async (input: AddTaskInput): Promise<AddTaskResult> => {
59
57
  if (parsedInput.url) {
60
58
  const parsed = parseGitHubPrUrl(parsedInput.url);
61
59
  if (parsed) {
62
- const token = await resolveGithubToken(parsedInput.workspaceRoot);
63
- let fetched: PrFetched | null = null;
64
- if (token) {
65
- try {
66
- fetched = await fetchGitHubPr(parsed, token);
67
- } catch {
68
- fetched = null;
69
- }
70
- }
60
+ const fetched = await fetchGitHubPr(parsed);
71
61
  prAttachment = buildPrAttachment(parsed, fetched ?? undefined);
72
62
  prTitle = fetched?.title ?? `PR #${parsed.number} ${parsed.repo.owner}/${parsed.repo.name}`;
73
63
  } else {
package/core/config.ts CHANGED
@@ -1,10 +1,8 @@
1
- import { access, mkdir, readFile } from "node:fs/promises";
1
+ import { access, mkdir } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import path from "node:path";
4
4
 
5
- import { parse as parseEnv } from "dotenv";
6
-
7
- import { APP_NAME, ENV_FILE } from "./constants";
5
+ import { APP_NAME } from "./constants";
8
6
  import { readJsonFile, writeJsonAtomic } from "./json";
9
7
 
10
8
  export type PluginEntry = {
@@ -36,9 +34,6 @@ export type PluginsConfig = {
36
34
 
37
35
  export type AppConfig = {
38
36
  autoCommit?: boolean;
39
- github?: {
40
- token?: string;
41
- };
42
37
  plugins?: PluginsConfig;
43
38
  };
44
39
 
@@ -92,60 +87,3 @@ export const resolveAutoCommitEnabled = async (workspaceRoot: string): Promise<b
92
87
  const globalConfig = await readConfigFile(resolveGlobalConfigPath());
93
88
  return globalConfig.autoCommit ?? false;
94
89
  };
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
-
121
- export const resolveGithubToken = async (workspaceRoot: string): Promise<string | null> => {
122
- // 1. Check environment variables first
123
- if (process.env.FLOWCAT_GITHUB_TOKEN) {
124
- return process.env.FLOWCAT_GITHUB_TOKEN;
125
- }
126
-
127
- if (process.env.IL_GITHUB_TOKEN) {
128
- return process.env.IL_GITHUB_TOKEN;
129
- }
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)
144
- const workspaceConfig = await readConfigFile(resolveWorkspaceConfigPath(workspaceRoot));
145
- if (workspaceConfig.github?.token) {
146
- return workspaceConfig.github.token;
147
- }
148
-
149
- const globalConfig = await readConfigFile(resolveGlobalConfigPath());
150
- return globalConfig.github?.token ?? null;
151
- };
package/core/initFlow.ts CHANGED
@@ -101,17 +101,12 @@ export const runInitFlow = async (options: InitFlowOptions): Promise<InitChoice>
101
101
  const currentConfig = await readConfigFile(configPath);
102
102
 
103
103
  const autoCommit = currentConfig.autoCommit ?? false;
104
- const githubToken = currentConfig.github?.token ?? null;
105
104
 
106
105
  await ensureWorkspaceLayout(choice.workspaceRoot);
107
106
 
108
107
  await updateConfigFile(configPath, (current) => ({
109
108
  ...current,
110
109
  autoCommit,
111
- github: {
112
- ...current.github,
113
- ...(githubToken ? { token: githubToken } : {}),
114
- },
115
110
  }));
116
111
 
117
112
  if (choice.kind === "repo" && choice.repoRoot) {
package/core/pr.ts CHANGED
@@ -1,8 +1,11 @@
1
- import { Octokit } from "octokit";
1
+ import { exec } from "node:child_process";
2
+ import { promisify } from "node:util";
2
3
 
3
4
  import type { PrAttachment, PrFetched } from "./schemas/pr";
4
5
  import { nowIso } from "./time";
5
6
 
7
+ const execAsync = promisify(exec);
8
+
6
9
  export type ParsedPr = {
7
10
  url: string;
8
11
  provider: "github";
@@ -54,36 +57,31 @@ export const buildPrAttachment = (parsed: ParsedPr, fetched?: PrFetched): PrAtta
54
57
  };
55
58
  };
56
59
 
57
- export const fetchGitHubPr = async (
58
- parsed: ParsedPr,
59
- token?: string | null,
60
- ): Promise<PrFetched | null> => {
60
+ export const fetchGitHubPr = async (parsed: ParsedPr): Promise<PrFetched | null> => {
61
61
  if (!parsed.repo || !parsed.number) {
62
62
  return null;
63
63
  }
64
64
 
65
- if (!token) {
66
- return null;
67
- }
68
-
69
- const octokit = new Octokit({ auth: token });
70
- const response = await octokit.rest.pulls.get({
71
- owner: parsed.repo.owner,
72
- repo: parsed.repo.name,
73
- pull_number: parsed.number,
74
- });
65
+ try {
66
+ const { stdout } = await execAsync(
67
+ `gh pr view ${parsed.url} --json title,author,state,isDraft,updatedAt`,
68
+ );
69
+ const data = JSON.parse(stdout);
75
70
 
76
- const data = response.data;
77
- const state = data.merged ? "merged" : data.state === "open" ? "open" : "closed";
71
+ // gh returns state as uppercase: "MERGED", "OPEN", "CLOSED"
72
+ const state = data.state.toLowerCase() as PrFetched["state"];
78
73
 
79
- return {
80
- at: nowIso(),
81
- title: data.title,
82
- author: {
83
- login: data.user?.login ?? "unknown",
84
- },
85
- state,
86
- draft: data.draft ?? false,
87
- updated_at: data.updated_at,
88
- };
74
+ return {
75
+ at: nowIso(),
76
+ title: data.title,
77
+ author: {
78
+ login: data.author.login,
79
+ },
80
+ state,
81
+ draft: data.isDraft,
82
+ updated_at: data.updatedAt,
83
+ };
84
+ } catch {
85
+ return null;
86
+ }
89
87
  };
@@ -1,4 +1,3 @@
1
- import { resolveGithubToken } from "./config";
2
1
  import { withWorkspaceLock } from "./lock";
3
2
  import { emitHook } from "./pluginManager";
4
3
  import { buildPrAttachment, fetchGitHubPr, parseGitHubPrUrl } from "./pr";
@@ -12,9 +11,9 @@ import { nowIso } from "./time";
12
11
  import type { TaskAction, TaskStatus } from "./types";
13
12
 
14
13
  export class PrWorkflowError extends Error {
15
- code: "INVALID_PR_URL" | "MISSING_TOKEN";
14
+ code: "INVALID_PR_URL" | "FETCH_FAILED";
16
15
 
17
- constructor(code: "INVALID_PR_URL" | "MISSING_TOKEN", message: string) {
16
+ constructor(code: "INVALID_PR_URL" | "FETCH_FAILED", message: string) {
18
17
  super(message);
19
18
  this.code = code;
20
19
  this.name = "PrWorkflowError";
@@ -69,15 +68,7 @@ export const attachPrToTask = async (input: {
69
68
  throw new PrWorkflowError("INVALID_PR_URL", "Invalid PR URL");
70
69
  }
71
70
 
72
- const token = await resolveGithubToken(input.workspaceRoot);
73
- let fetched: PrFetched | null = null;
74
- if (token) {
75
- try {
76
- fetched = await fetchGitHubPr(parsed, token);
77
- } catch {
78
- fetched = null;
79
- }
80
- }
71
+ const fetched = await fetchGitHubPr(parsed);
81
72
  const stored = await resolveTask(input.workspaceRoot, input.identifier);
82
73
 
83
74
  const updated: Task = {
@@ -126,10 +117,12 @@ export const refreshPrForTask = async (input: {
126
117
  throw new Error("Invalid PR URL");
127
118
  }
128
119
 
129
- const token = await resolveGithubToken(input.workspaceRoot);
130
- const fetched = await fetchGitHubPr(parsed, token);
120
+ const fetched = await fetchGitHubPr(parsed);
131
121
  if (!fetched) {
132
- throw new PrWorkflowError("MISSING_TOKEN", "No GitHub token configured");
122
+ throw new PrWorkflowError(
123
+ "FETCH_FAILED",
124
+ "Failed to fetch PR. Make sure gh CLI is installed and authenticated.",
125
+ );
133
126
  }
134
127
 
135
128
  const baseUpdated: Task = {