workspacecord 1.0.2 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workspacecord",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Turn Discord into a local workspace control plane for multi-agent coding sessions.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -36,15 +36,18 @@
36
36
  "dependencies": {
37
37
  "@anthropic-ai/claude-agent-sdk": "^0.2.85",
38
38
  "@clack/prompts": "^1.1.0",
39
+ "cac": "^7.0.0",
39
40
  "configstore": "^8.0.0",
40
41
  "discord.js": "^14.25.1",
41
42
  "execa": "^9.6.1",
43
+ "pino": "^10.3.1",
42
44
  "sharp": "^0.34.5"
43
45
  },
44
46
  "devDependencies": {
45
47
  "@eslint/js": "^10.0.1",
46
48
  "@types/configstore": "^6.0.2",
47
49
  "@types/node": "^25.5.0",
50
+ "@vitest/coverage-v8": "^4.1.2",
48
51
  "eslint": "^10.1.0",
49
52
  "eslint-config-prettier": "^10.1.8",
50
53
  "globals": "^17.4.0",
@@ -62,15 +65,17 @@
62
65
  },
63
66
  "scripts": {
64
67
  "build": "tsup",
65
- "start": "node --experimental-strip-types src/cli.ts",
68
+ "start": "NODE_ENV=development node --experimental-strip-types src/cli.ts",
66
69
  "dev": "tsup --watch",
67
70
  "typecheck": "tsc --noEmit",
68
71
  "test": "vitest run",
72
+ "test:coverage": "vitest run --coverage",
69
73
  "test:integration:smoke": "node --experimental-strip-types scripts/integration-smoke.ts",
70
74
  "test:multi-session:smoke": "node --experimental-strip-types scripts/multi-session-smoke.ts",
71
75
  "test:session-sync:smoke": "node --experimental-strip-types scripts/session-sync-smoke.ts",
72
76
  "test:monitor:e2e": "node --experimental-strip-types scripts/monitor-e2e.ts",
73
77
  "test:acceptance:local": "node --experimental-strip-types scripts/local-acceptance-suite.ts",
78
+ "release": "node --experimental-strip-types scripts/release.ts",
74
79
  "lint": "eslint .",
75
80
  "lint:fix": "eslint . --fix",
76
81
  "format": "prettier --write .",
@@ -1,130 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/utils.ts
4
- import { resolve, isAbsolute } from "path";
5
- import { homedir } from "os";
6
- function sanitizeName(name) {
7
- return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 50) || "session";
8
- }
9
- function resolvePath(p) {
10
- if (p.startsWith("~/") || p === "~") {
11
- return p.replace("~", homedir());
12
- }
13
- return isAbsolute(p) ? p : resolve(process.cwd(), p);
14
- }
15
- function isPathAllowed(path, allowedPaths) {
16
- if (allowedPaths.length === 0) return true;
17
- const resolved = resolvePath(path);
18
- return allowedPaths.some((allowed) => {
19
- const resolvedAllowed = resolvePath(allowed);
20
- return resolved === resolvedAllowed || resolved.startsWith(resolvedAllowed + "/");
21
- });
22
- }
23
- function projectNameFromChannel(channelName) {
24
- return channelName;
25
- }
26
- function formatDuration(ms) {
27
- const s = Math.floor(ms / 1e3);
28
- if (s < 60) return `${s}s`;
29
- const m = Math.floor(s / 60);
30
- if (m < 60) return `${m}m ${s % 60}s`;
31
- const h = Math.floor(m / 60);
32
- return `${h}h ${m % 60}m`;
33
- }
34
- function formatRelative(ts) {
35
- const diff = Date.now() - ts;
36
- if (diff < 6e4) return "just now";
37
- if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
38
- if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
39
- return `${Math.floor(diff / 864e5)}d ago`;
40
- }
41
- function truncate(s, max) {
42
- if (s.length <= max) return s;
43
- return s.slice(0, max - 1) + "\u2026";
44
- }
45
- function isUserAllowed(userId, allowedUsers, allowAll) {
46
- if (allowAll) return true;
47
- if (allowedUsers.length === 0) return true;
48
- return allowedUsers.includes(userId);
49
- }
50
- var ABORT_PATTERNS = ["abort", "cancel", "interrupt", "killed", "signal"];
51
- function isAbortError(err) {
52
- if (err instanceof Error && err.name === "AbortError") return true;
53
- const msg = (err.message || "").toLowerCase();
54
- return ABORT_PATTERNS.some((p) => msg.includes(p));
55
- }
56
- function isAbortErrorMessage(messages) {
57
- return messages.some((m) => ABORT_PATTERNS.some((p) => m.toLowerCase().includes(p)));
58
- }
59
- function detectNumberedOptions(text) {
60
- const lines = text.trim().split("\n");
61
- const options = [];
62
- const optionRegex = /^\s*(\d+)[.)]\s+(.+)$/;
63
- let firstOptionLine = -1;
64
- let lastOptionLine = -1;
65
- for (let i = 0; i < lines.length; i++) {
66
- const match = lines[i].match(optionRegex);
67
- if (match) {
68
- if (firstOptionLine === -1) firstOptionLine = i;
69
- lastOptionLine = i;
70
- options.push(match[2].trim());
71
- }
72
- }
73
- if (options.length < 2 || options.length > 6) return null;
74
- if (options.some((o) => o.length > 80)) return null;
75
- const linesAfter = lines.slice(lastOptionLine + 1).filter((l) => l.trim()).length;
76
- if (linesAfter > 3) return null;
77
- const preamble = lines.slice(0, firstOptionLine).join(" ").toLowerCase();
78
- const hasQuestion = /\?\s*$/.test(preamble.trim()) || /\b(which|choose|select|pick|prefer|would you like|how would you|what approach|option)\b/.test(
79
- preamble
80
- );
81
- return hasQuestion ? options : null;
82
- }
83
- function detectYesNoPrompt(text) {
84
- const lower = text.toLowerCase();
85
- return /\b(y\/n|yes\/no|confirm|proceed)\b/.test(lower) || /\?\s*$/.test(text.trim()) && /\b(should|would you|do you want|shall)\b/.test(lower);
86
- }
87
- function formatUptime(startTime) {
88
- const ms = Date.now() - startTime;
89
- const seconds = Math.floor(ms / 1e3);
90
- const minutes = Math.floor(seconds / 60);
91
- const hours = Math.floor(minutes / 60);
92
- const days = Math.floor(hours / 24);
93
- if (days > 0) return `${days}d ${hours % 24}h`;
94
- if (hours > 0) return `${hours}h ${minutes % 60}m`;
95
- if (minutes > 0) return `${minutes}m`;
96
- return `${seconds}s`;
97
- }
98
- function splitMessage(text, max = 1900) {
99
- if (text.length <= max) return [text];
100
- const chunks = [];
101
- let i = 0;
102
- while (i < text.length) {
103
- chunks.push(text.slice(i, i + max));
104
- i += max;
105
- }
106
- return chunks;
107
- }
108
- function formatCost(usd) {
109
- if (usd === 0) return "$0.00";
110
- if (usd < 0.01) return `$${usd.toFixed(4)}`;
111
- return `$${usd.toFixed(2)}`;
112
- }
113
-
114
- export {
115
- sanitizeName,
116
- resolvePath,
117
- isPathAllowed,
118
- projectNameFromChannel,
119
- formatDuration,
120
- formatRelative,
121
- truncate,
122
- isUserAllowed,
123
- isAbortError,
124
- isAbortErrorMessage,
125
- detectNumberedOptions,
126
- detectYesNoPrompt,
127
- formatUptime,
128
- splitMessage,
129
- formatCost
130
- };
@@ -1,36 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- detectNumberedOptions,
4
- detectYesNoPrompt,
5
- formatCost,
6
- formatDuration,
7
- formatRelative,
8
- formatUptime,
9
- isAbortError,
10
- isAbortErrorMessage,
11
- isPathAllowed,
12
- isUserAllowed,
13
- projectNameFromChannel,
14
- resolvePath,
15
- sanitizeName,
16
- splitMessage,
17
- truncate
18
- } from "./chunk-WE4X3JB3.js";
19
- import "./chunk-K3NQKI34.js";
20
- export {
21
- detectNumberedOptions,
22
- detectYesNoPrompt,
23
- formatCost,
24
- formatDuration,
25
- formatRelative,
26
- formatUptime,
27
- isAbortError,
28
- isAbortErrorMessage,
29
- isPathAllowed,
30
- isUserAllowed,
31
- projectNameFromChannel,
32
- resolvePath,
33
- sanitizeName,
34
- splitMessage,
35
- truncate
36
- };