@fureworks/scope 0.1.0 → 0.2.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.
Files changed (80) hide show
  1. package/README.md +129 -48
  2. package/dist/cli/config.d.ts +8 -1
  3. package/dist/cli/config.d.ts.map +1 -1
  4. package/dist/cli/config.js +368 -42
  5. package/dist/cli/config.js.map +1 -1
  6. package/dist/cli/init.d.ts +2 -0
  7. package/dist/cli/init.d.ts.map +1 -0
  8. package/dist/cli/init.js +15 -0
  9. package/dist/cli/init.js.map +1 -0
  10. package/dist/cli/notifications.d.ts +7 -0
  11. package/dist/cli/notifications.d.ts.map +1 -0
  12. package/dist/cli/notifications.js +77 -0
  13. package/dist/cli/notifications.js.map +1 -0
  14. package/dist/cli/onboard.d.ts.map +1 -1
  15. package/dist/cli/onboard.js +2 -1
  16. package/dist/cli/onboard.js.map +1 -1
  17. package/dist/cli/plan.d.ts +7 -0
  18. package/dist/cli/plan.d.ts.map +1 -0
  19. package/dist/cli/plan.js +111 -0
  20. package/dist/cli/plan.js.map +1 -0
  21. package/dist/cli/review.d.ts +6 -0
  22. package/dist/cli/review.d.ts.map +1 -0
  23. package/dist/cli/review.js +167 -0
  24. package/dist/cli/review.js.map +1 -0
  25. package/dist/cli/snooze.d.ts +12 -0
  26. package/dist/cli/snooze.d.ts.map +1 -0
  27. package/dist/cli/snooze.js +155 -0
  28. package/dist/cli/snooze.js.map +1 -0
  29. package/dist/cli/today.d.ts.map +1 -1
  30. package/dist/cli/today.js +69 -9
  31. package/dist/cli/today.js.map +1 -1
  32. package/dist/cli/tune.d.ts +8 -0
  33. package/dist/cli/tune.d.ts.map +1 -0
  34. package/dist/cli/tune.js +62 -0
  35. package/dist/cli/tune.js.map +1 -0
  36. package/dist/engine/prioritize.d.ts +10 -2
  37. package/dist/engine/prioritize.d.ts.map +1 -1
  38. package/dist/engine/prioritize.js +156 -25
  39. package/dist/engine/prioritize.js.map +1 -1
  40. package/dist/index.js +48 -5
  41. package/dist/index.js.map +1 -1
  42. package/dist/notifications/index.d.ts.map +1 -1
  43. package/dist/notifications/index.js +6 -10
  44. package/dist/notifications/index.js.map +1 -1
  45. package/dist/sources/activity.d.ts +32 -0
  46. package/dist/sources/activity.d.ts.map +1 -0
  47. package/dist/sources/activity.js +101 -0
  48. package/dist/sources/activity.js.map +1 -0
  49. package/dist/sources/calendar.d.ts +6 -0
  50. package/dist/sources/calendar.d.ts.map +1 -1
  51. package/dist/sources/calendar.js +114 -0
  52. package/dist/sources/calendar.js.map +1 -1
  53. package/dist/store/config.d.ts +8 -0
  54. package/dist/store/config.d.ts.map +1 -1
  55. package/dist/store/config.js +22 -0
  56. package/dist/store/config.js.map +1 -1
  57. package/dist/store/muted.d.ts +17 -0
  58. package/dist/store/muted.d.ts.map +1 -0
  59. package/dist/store/muted.js +55 -0
  60. package/dist/store/muted.js.map +1 -0
  61. package/dist/store/snapshot.d.ts +12 -0
  62. package/dist/store/snapshot.d.ts.map +1 -0
  63. package/dist/store/snapshot.js +41 -0
  64. package/dist/store/snapshot.js.map +1 -0
  65. package/package.json +8 -2
  66. package/src/cli/config.ts +0 -66
  67. package/src/cli/context.ts +0 -109
  68. package/src/cli/daemon.ts +0 -217
  69. package/src/cli/onboard.ts +0 -335
  70. package/src/cli/status.ts +0 -77
  71. package/src/cli/switch.ts +0 -93
  72. package/src/cli/today.ts +0 -114
  73. package/src/engine/prioritize.ts +0 -257
  74. package/src/index.ts +0 -58
  75. package/src/notifications/index.ts +0 -42
  76. package/src/sources/calendar.ts +0 -170
  77. package/src/sources/git.ts +0 -168
  78. package/src/sources/issues.ts +0 -62
  79. package/src/store/config.ts +0 -104
  80. package/tsconfig.json +0 -19
package/src/cli/daemon.ts DELETED
@@ -1,217 +0,0 @@
1
- import chalk from "chalk";
2
- import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
- import { spawn } from "node:child_process";
4
- import { join } from "node:path";
5
- import { configExists, ensureScopeDir, getScopeDir, loadConfig, saveConfig } from "../store/config.js";
6
- import { scanAllRepos } from "../sources/git.js";
7
- import { getCalendarToday } from "../sources/calendar.js";
8
- import { scanAssignedIssues } from "../sources/issues.js";
9
- import { prioritize } from "../engine/prioritize.js";
10
- import { notify } from "../notifications/index.js";
11
-
12
- const PID_PATH = join(getScopeDir(), "daemon.pid");
13
- const NOTIFIED_PATH = join(getScopeDir(), "notified.json");
14
- const DEBOUNCE_MS = 60 * 60 * 1000;
15
-
16
- type NotifiedState = Record<string, string>;
17
-
18
- function isProcessRunning(pid: number): boolean {
19
- try {
20
- process.kill(pid, 0);
21
- return true;
22
- } catch {
23
- return false;
24
- }
25
- }
26
-
27
- function readPid(): number | null {
28
- if (!existsSync(PID_PATH)) return null;
29
- const raw = readFileSync(PID_PATH, "utf-8").trim();
30
- const pid = Number.parseInt(raw, 10);
31
- return Number.isFinite(pid) ? pid : null;
32
- }
33
-
34
- function removePidFile(): void {
35
- if (existsSync(PID_PATH)) {
36
- unlinkSync(PID_PATH);
37
- }
38
- }
39
-
40
- function loadNotifiedState(): NotifiedState {
41
- if (!existsSync(NOTIFIED_PATH)) return {};
42
- try {
43
- return JSON.parse(readFileSync(NOTIFIED_PATH, "utf-8")) as NotifiedState;
44
- } catch {
45
- return {};
46
- }
47
- }
48
-
49
- function saveNotifiedState(state: NotifiedState): void {
50
- writeFileSync(NOTIFIED_PATH, JSON.stringify(state, null, 2), "utf-8");
51
- }
52
-
53
- async function runSignalCheck(): Promise<void> {
54
- if (!configExists()) return;
55
-
56
- const config = loadConfig();
57
- const gitSignals = await scanAllRepos(config.repos);
58
-
59
- let calendarEvents: Awaited<ReturnType<typeof getCalendarToday>> = null;
60
- if (config.calendar.enabled) {
61
- calendarEvents = await getCalendarToday();
62
- }
63
-
64
- const issueScan = await scanAssignedIssues();
65
- const events = calendarEvents?.events ?? [];
66
- const freeBlocks = calendarEvents?.freeBlocks ?? [];
67
- const result = prioritize(gitSignals, events, freeBlocks, issueScan.issues);
68
-
69
- const candidates = [...result.now, ...result.today].filter((item) => item.score >= 8);
70
- if (candidates.length === 0) return;
71
-
72
- ensureScopeDir();
73
- const state = loadNotifiedState();
74
- const nowMs = Date.now();
75
- let changed = false;
76
-
77
- for (const item of candidates) {
78
- const id = `${item.source}:${item.label}`;
79
- const lastNotified = state[id] ? new Date(state[id]).getTime() : 0;
80
- if (lastNotified && nowMs - lastNotified < DEBOUNCE_MS) {
81
- continue;
82
- }
83
-
84
- notify("Scope: Action Needed", `${item.emoji} ${item.label} — ${item.detail}`);
85
- state[id] = new Date(nowMs).toISOString();
86
- changed = true;
87
- }
88
-
89
- if (changed) {
90
- saveNotifiedState(state);
91
- }
92
- }
93
-
94
- function markDaemonEnabled(enabled: boolean): void {
95
- if (!configExists()) return;
96
- const config = loadConfig();
97
- config.daemon.enabled = enabled;
98
- saveConfig(config);
99
- }
100
-
101
- export async function daemonCommand(action: string): Promise<void> {
102
- switch (action) {
103
- case "start":
104
- await startDaemon();
105
- return;
106
- case "stop":
107
- stopDaemon();
108
- return;
109
- case "status":
110
- showDaemonStatus();
111
- return;
112
- case "run":
113
- await runDaemonLoop();
114
- return;
115
- default:
116
- console.log(chalk.yellow(`\n Unknown daemon action: ${action}`));
117
- console.log(chalk.dim(" Use: scope daemon start|stop|status\n"));
118
- }
119
- }
120
-
121
- async function startDaemon(): Promise<void> {
122
- if (!configExists()) {
123
- console.log(chalk.yellow("\n Scope isn't set up yet. Run `scope onboard` first.\n"));
124
- return;
125
- }
126
-
127
- ensureScopeDir();
128
-
129
- const existingPid = readPid();
130
- if (existingPid && isProcessRunning(existingPid)) {
131
- console.log(chalk.green(`\n Daemon already running (PID ${existingPid}).\n`));
132
- return;
133
- }
134
- removePidFile();
135
-
136
- const child = spawn(process.execPath, [process.argv[1], "daemon", "run"], {
137
- detached: true,
138
- stdio: "ignore",
139
- });
140
- child.unref();
141
-
142
- writeFileSync(PID_PATH, String(child.pid), "utf-8");
143
- markDaemonEnabled(true);
144
-
145
- console.log(chalk.green(`\n Daemon started (PID ${child.pid}).\n`));
146
- }
147
-
148
- function stopDaemon(): void {
149
- const pid = readPid();
150
- if (!pid) {
151
- console.log(chalk.dim("\n Daemon is not running.\n"));
152
- markDaemonEnabled(false);
153
- return;
154
- }
155
-
156
- if (!isProcessRunning(pid)) {
157
- removePidFile();
158
- console.log(chalk.dim("\n Daemon was not running (stale PID removed).\n"));
159
- markDaemonEnabled(false);
160
- return;
161
- }
162
-
163
- try {
164
- process.kill(pid);
165
- removePidFile();
166
- markDaemonEnabled(false);
167
- console.log(chalk.green(`\n Daemon stopped (PID ${pid}).\n`));
168
- } catch {
169
- console.log(chalk.yellow(`\n Could not stop daemon PID ${pid}.\n`));
170
- }
171
- }
172
-
173
- function showDaemonStatus(): void {
174
- const config = loadConfig();
175
- const pid = readPid();
176
-
177
- if (pid && isProcessRunning(pid)) {
178
- console.log(chalk.green(`\n Daemon running (PID ${pid}).`));
179
- console.log(chalk.dim(` Interval: ${config.daemon.intervalMinutes} minutes\n`));
180
- return;
181
- }
182
-
183
- if (pid) {
184
- removePidFile();
185
- }
186
-
187
- console.log(chalk.dim("\n Daemon is not running."));
188
- console.log(chalk.dim(` Interval: ${config.daemon.intervalMinutes} minutes\n`));
189
- }
190
-
191
- async function runDaemonLoop(): Promise<void> {
192
- ensureScopeDir();
193
- writeFileSync(PID_PATH, String(process.pid), "utf-8");
194
-
195
- const cleanup = () => {
196
- const pid = readPid();
197
- if (pid === process.pid) {
198
- removePidFile();
199
- }
200
- process.exit(0);
201
- };
202
-
203
- process.on("SIGTERM", cleanup);
204
- process.on("SIGINT", cleanup);
205
-
206
- while (true) {
207
- try {
208
- await runSignalCheck();
209
- } catch {
210
- // Keep daemon alive even if checks fail.
211
- }
212
-
213
- const config = configExists() ? loadConfig() : null;
214
- const intervalMinutes = Math.max(1, config?.daemon.intervalMinutes ?? 15);
215
- await new Promise((resolve) => setTimeout(resolve, intervalMinutes * 60 * 1000));
216
- }
217
- }
@@ -1,335 +0,0 @@
1
- import chalk from "chalk";
2
- import { createInterface } from "node:readline";
3
- import { execSync } from "node:child_process";
4
- import { existsSync, readdirSync, statSync } from "node:fs";
5
- import { resolve, join, basename } from "node:path";
6
- import { homedir } from "node:os";
7
- import { ScopeConfig, saveConfig, ensureScopeDir } from "../store/config.js";
8
-
9
- function ask(rl: ReturnType<typeof createInterface>, question: string): Promise<string> {
10
- return new Promise((resolve) => {
11
- rl.question(question, (answer) => {
12
- resolve(answer.trim());
13
- });
14
- });
15
- }
16
-
17
- function checkCommand(cmd: string): boolean {
18
- try {
19
- execSync(`which ${cmd}`, { stdio: "pipe" });
20
- return true;
21
- } catch {
22
- return false;
23
- }
24
- }
25
-
26
- function findGitRepos(): { path: string; name: string }[] {
27
- const home = homedir();
28
- const repos: { path: string; name: string }[] = [];
29
-
30
- // Scan all top-level directories in home for git repos (1-2 levels deep)
31
- const searchRoots: string[] = [home];
32
-
33
- // Collect all immediate subdirectories of home as potential search roots
34
- try {
35
- const homeEntries = readdirSync(home);
36
- for (const entry of homeEntries) {
37
- if (entry.startsWith(".")) continue; // Skip dotfiles/dirs
38
- const fullPath = join(home, entry);
39
- try {
40
- if (statSync(fullPath).isDirectory()) {
41
- // Check if this dir itself is a repo
42
- if (existsSync(join(fullPath, ".git"))) {
43
- repos.push({ path: fullPath, name: entry });
44
- } else {
45
- // Search one level deeper inside this directory
46
- searchRoots.push(fullPath);
47
- }
48
- }
49
- } catch {
50
- // Skip permission errors
51
- }
52
- }
53
- } catch {
54
- // Skip if home is inaccessible
55
- }
56
-
57
- // Scan each search root (one level deep)
58
- for (const dir of searchRoots) {
59
- if (dir === home) continue; // Already scanned top-level
60
- try {
61
- const entries = readdirSync(dir);
62
- for (const entry of entries) {
63
- if (entry.startsWith(".")) continue;
64
- const fullPath = join(dir, entry);
65
- try {
66
- if (
67
- statSync(fullPath).isDirectory() &&
68
- existsSync(join(fullPath, ".git"))
69
- ) {
70
- repos.push({ path: fullPath, name: `${basename(dir)}/${entry}` });
71
- }
72
- } catch {
73
- // Skip permission errors
74
- }
75
- }
76
- } catch {
77
- // Skip inaccessible dirs
78
- }
79
- }
80
-
81
- // Dedupe by path
82
- const seen = new Set<string>();
83
- return repos.filter((r) => {
84
- if (seen.has(r.path)) return false;
85
- seen.add(r.path);
86
- return true;
87
- });
88
- }
89
-
90
- function askSelection(
91
- rl: ReturnType<typeof createInterface>,
92
- question: string,
93
- options: { label: string; value: string }[]
94
- ): Promise<string[]> {
95
- return new Promise((resolvePromise) => {
96
- const selected = new Set<number>();
97
-
98
- console.log(question);
99
- options.forEach((opt, i) => {
100
- console.log(chalk.dim(` ${i + 1}) ${opt.label}`));
101
- });
102
- console.log(
103
- chalk.dim(
104
- `\n Enter numbers separated by commas (e.g. 1,3,5), 'all' for everything, or 'none' to skip`
105
- )
106
- );
107
-
108
- rl.question(" ? Select: ", (answer) => {
109
- const trimmed = answer.trim().toLowerCase();
110
-
111
- if (trimmed === "all") {
112
- resolvePromise(options.map((o) => o.value));
113
- return;
114
- }
115
-
116
- if (trimmed === "none" || trimmed === "") {
117
- resolvePromise([]);
118
- return;
119
- }
120
-
121
- const indices = trimmed
122
- .split(",")
123
- .map((s) => parseInt(s.trim(), 10) - 1)
124
- .filter((i) => i >= 0 && i < options.length);
125
-
126
- resolvePromise(indices.map((i) => options[i].value));
127
- });
128
- });
129
- }
130
-
131
- export async function onboardCommand(): Promise<void> {
132
- const rl = createInterface({
133
- input: process.stdin,
134
- output: process.stdout,
135
- });
136
-
137
- console.log("");
138
- console.log(chalk.bold(" Welcome to Scope — let's get you set up.\n"));
139
-
140
- const config: ScopeConfig = {
141
- repos: [],
142
- projects: {},
143
- calendar: { enabled: false, backend: "gws" },
144
- daemon: { enabled: false, intervalMinutes: 15 },
145
- };
146
-
147
- // Step 1: Git repos
148
- console.log(chalk.bold(" Step 1/4: Git repos"));
149
- console.log(chalk.dim(" ─────────────────────"));
150
- console.log(chalk.dim(" Scanning for repos...\n"));
151
-
152
- const foundRepos = findGitRepos();
153
-
154
- if (foundRepos.length > 0) {
155
- console.log(
156
- chalk.green(` Found ${foundRepos.length} repo${foundRepos.length !== 1 ? "s" : ""}:\n`)
157
- );
158
-
159
- const selectedPaths = await askSelection(
160
- rl,
161
- "",
162
- foundRepos.map((r) => ({
163
- label: `${r.name} ${chalk.dim(`(${r.path})`)}`,
164
- value: r.path,
165
- }))
166
- );
167
-
168
- config.repos.push(...selectedPaths);
169
-
170
- if (selectedPaths.length > 0) {
171
- console.log(
172
- chalk.green(`\n ✓ Added ${selectedPaths.length} repo${selectedPaths.length !== 1 ? "s" : ""}`)
173
- );
174
- }
175
- } else {
176
- console.log(chalk.dim(" No repos found in common directories."));
177
- }
178
-
179
- // Only ask for manual paths if no repos were found automatically
180
- if (foundRepos.length === 0) {
181
- console.log("");
182
- let addingMore = true;
183
- while (addingMore) {
184
- const input = await ask(
185
- rl,
186
- " ? Add a repo path (or 'done'): "
187
- );
188
-
189
- if (input.toLowerCase() === "done" || input === "") {
190
- addingMore = false;
191
- } else if (input.startsWith("http://") || input.startsWith("https://") || input.startsWith("git@")) {
192
- console.log(chalk.yellow(` ✗ Scope needs local paths, not URLs.`));
193
- console.log(chalk.dim(` Clone it first, then add the local path`));
194
- } else {
195
- const resolved = resolve(input.replace(/^~/, process.env.HOME || "~"));
196
- if (existsSync(resolved)) {
197
- config.repos.push(resolved);
198
- console.log(chalk.green(` ✓ Added ${resolved}`));
199
- } else {
200
- console.log(chalk.yellow(` ✗ Path not found: ${resolved}`));
201
- }
202
- }
203
- }
204
- }
205
-
206
- console.log(
207
- chalk.green(`\n ✓ Watching ${config.repos.length} repo${config.repos.length !== 1 ? "s" : ""}\n`)
208
- );
209
-
210
- // Step 2: GitHub CLI
211
- console.log(chalk.bold(" Step 2/4: GitHub CLI"));
212
- console.log(chalk.dim(" ─────────────────────"));
213
-
214
- const hasGh = checkCommand("gh");
215
- if (hasGh) {
216
- console.log(chalk.green(" Checking for gh CLI... ✓ Found"));
217
- try {
218
- const authStatus = execSync("gh auth status 2>&1", {
219
- encoding: "utf-8",
220
- });
221
- if (authStatus.includes("Logged in")) {
222
- console.log(chalk.green(" Checking auth... ✓ Logged in"));
223
- } else {
224
- console.log(
225
- chalk.yellow(" Checking auth... ✗ Not authenticated")
226
- );
227
- console.log(chalk.dim(" Run 'gh auth login' to enable PR data\n"));
228
- }
229
- } catch {
230
- console.log(chalk.yellow(" Checking auth... ✗ Not authenticated"));
231
- console.log(chalk.dim(" Run 'gh auth login' to enable PR data\n"));
232
- }
233
- } else {
234
- console.log(chalk.yellow(" gh CLI not found — PR data will be skipped"));
235
- console.log(chalk.dim(" Install: https://cli.github.com/\n"));
236
- }
237
-
238
- console.log(chalk.green(" ✓ GitHub PR data " + (hasGh ? "available" : "skipped") + "\n"));
239
-
240
- // Step 3: Google Calendar
241
- console.log(chalk.bold(" Step 3/4: Google Calendar (optional)"));
242
- console.log(chalk.dim(" ─────────────────────"));
243
-
244
- const hasGws = checkCommand("gws");
245
- if (hasGws) {
246
- console.log(chalk.green(" Checking for gws CLI... ✓ Found"));
247
- const enableCal = await ask(
248
- rl,
249
- " ? Enable calendar integration? (Y/n): "
250
- );
251
- if (enableCal.toLowerCase() !== "n") {
252
- config.calendar.enabled = true;
253
- console.log(chalk.green("\n ✓ Calendar enabled\n"));
254
- } else {
255
- console.log(chalk.dim("\n Calendar skipped. Enable later with 'scope config calendar'\n"));
256
- }
257
- } else {
258
- console.log(chalk.yellow(" gws CLI not found — calendar will be skipped"));
259
- console.log(chalk.dim(" Install: npm install -g @googleworkspace/cli"));
260
- console.log(chalk.dim(" Enable later with 'scope config calendar'\n"));
261
- }
262
-
263
- // Step 4: Projects — group repos under names
264
- console.log(chalk.bold(" Step 4/4: Projects"));
265
- console.log(chalk.dim(" ─────────────────────"));
266
- console.log(chalk.dim(" Projects group your repos for context switching."));
267
- console.log(chalk.dim(" e.g. 'wtl' = your work repos, 'personal' = side projects\n"));
268
-
269
- if (config.repos.length > 0) {
270
- let assigningProjects = true;
271
- const unassigned = [...config.repos];
272
-
273
- while (assigningProjects && unassigned.length > 0) {
274
- const projectName = await ask(rl, " ? Project name (or 'done'): ");
275
-
276
- if (projectName.toLowerCase() === "done" || projectName === "") {
277
- assigningProjects = false;
278
- continue;
279
- }
280
-
281
- console.log(chalk.dim("\n Which repos belong to this project?\n"));
282
- unassigned.forEach((r, i) => {
283
- const name = r.split("/").slice(-2).join("/");
284
- console.log(chalk.dim(` ${i + 1}) ${name}`));
285
- });
286
- console.log(chalk.dim(`\n Enter numbers (e.g. 1,3,5), 'all', or 'none'`));
287
-
288
- const pick = await ask(rl, " ? Select: ");
289
- const trimmed = pick.trim().toLowerCase();
290
-
291
- let selectedPaths: string[] = [];
292
- if (trimmed === "all") {
293
- selectedPaths = [...unassigned];
294
- } else if (trimmed === "none" || trimmed === "") {
295
- // skip
296
- } else {
297
- const indices = trimmed
298
- .split(",")
299
- .map((s) => parseInt(s.trim(), 10) - 1)
300
- .filter((i) => i >= 0 && i < unassigned.length);
301
- selectedPaths = indices.map((i) => unassigned[i]);
302
- }
303
-
304
- if (selectedPaths.length > 0) {
305
- config.projects[projectName] = { path: selectedPaths[0], repos: selectedPaths };
306
- // Remove assigned repos from unassigned
307
- for (const p of selectedPaths) {
308
- const idx = unassigned.indexOf(p);
309
- if (idx !== -1) unassigned.splice(idx, 1);
310
- }
311
- console.log(
312
- chalk.green(`\n ✓ Project "${projectName}" — ${selectedPaths.length} repo${selectedPaths.length !== 1 ? "s" : ""}\n`)
313
- );
314
- }
315
-
316
- if (unassigned.length > 0) {
317
- console.log(chalk.dim(` ${unassigned.length} repo${unassigned.length !== 1 ? "s" : ""} unassigned. Add another project or 'done'.\n`));
318
- }
319
- }
320
- } else {
321
- console.log(chalk.dim(" No repos to group. Add projects later with 'scope config projects'\n"));
322
- }
323
-
324
- // Save
325
- ensureScopeDir();
326
- saveConfig(config);
327
-
328
- console.log(chalk.dim(" ─────────────────────"));
329
- console.log(chalk.bold.green(" Setup complete!"));
330
- console.log(chalk.dim(` Config saved to ~/.scope/config.toml\n`));
331
- console.log(` Try: ${chalk.bold("scope today")}`);
332
- console.log(chalk.dim(`\n Tip: run 'npm link' in this directory to use 'scope' globally\n`));
333
-
334
- rl.close();
335
- }
package/src/cli/status.ts DELETED
@@ -1,77 +0,0 @@
1
- import chalk from "chalk";
2
- import { loadConfig, configExists } from "../store/config.js";
3
- import { scanAllRepos } from "../sources/git.js";
4
-
5
- interface StatusOptions {
6
- json?: boolean;
7
- }
8
-
9
- export async function statusCommand(options: StatusOptions): Promise<void> {
10
- if (!configExists()) {
11
- console.log(
12
- chalk.yellow(" Scope isn't set up yet. Run `scope onboard` to get started.\n")
13
- );
14
- return;
15
- }
16
-
17
- const config = loadConfig();
18
- const gitSignals = await scanAllRepos(config.repos);
19
-
20
- if (options.json) {
21
- console.log(JSON.stringify({ repos: gitSignals, config }, null, 2));
22
- return;
23
- }
24
-
25
- console.log("");
26
- console.log(chalk.bold(" Scope Status"));
27
- console.log(chalk.dim(" ─────────────────────\n"));
28
-
29
- // Repos
30
- console.log(chalk.bold(" Repos"));
31
- if (gitSignals.length === 0) {
32
- console.log(chalk.dim(" No repos configured or accessible.\n"));
33
- } else {
34
- for (const signal of gitSignals) {
35
- const status: string[] = [];
36
- if (signal.uncommittedFiles > 0) {
37
- status.push(
38
- chalk.yellow(`${signal.uncommittedFiles} uncommitted`)
39
- );
40
- }
41
- if (signal.openPRs.length > 0) {
42
- status.push(chalk.blue(`${signal.openPRs.length} PRs`));
43
- }
44
- if (signal.staleBranches.length > 0) {
45
- status.push(
46
- chalk.dim(`${signal.staleBranches.length} stale branches`)
47
- );
48
- }
49
- if (status.length === 0) {
50
- status.push(chalk.green("clean"));
51
- }
52
-
53
- console.log(
54
- ` ${signal.repo} ${chalk.dim(`(${signal.branch})`)} — ${status.join(", ")}`
55
- );
56
- }
57
- console.log("");
58
- }
59
-
60
- // Projects
61
- const projectNames = Object.keys(config.projects);
62
- if (projectNames.length > 0) {
63
- console.log(chalk.bold(" Projects"));
64
- for (const name of projectNames) {
65
- const p = config.projects[name];
66
- console.log(` ${name} ${chalk.dim(`→ ${p.path}`)}`);
67
- }
68
- console.log("");
69
- }
70
-
71
- // Calendar
72
- console.log(chalk.bold(" Integrations"));
73
- console.log(
74
- ` Calendar: ${config.calendar.enabled ? chalk.green("enabled") : chalk.dim("disabled")}`
75
- );
76
- console.log("");
77
- }