workspacecord 1.0.3 → 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.
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ archiveSession,
4
+ checkAutoArchive,
5
+ getArchivedSessions,
6
+ isArchivedProviderSession,
7
+ loadArchived
8
+ } from "./chunk-PUKMHDXS.js";
9
+ import "./chunk-IFSOU4XI.js";
10
+ import "./chunk-D6J3X35H.js";
11
+ import "./chunk-7KESUGJP.js";
12
+ import "./chunk-CBNENUW6.js";
13
+ import "./chunk-52OOARML.js";
14
+ import "./chunk-K3NQKI34.js";
15
+ export {
16
+ archiveSession,
17
+ checkAutoArchive,
18
+ getArchivedSessions,
19
+ isArchivedProviderSession,
20
+ loadArchived
21
+ };
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ fetchRegisteredAttachments
4
+ } from "./chunk-Q2AJ4UEK.js";
5
+ import "./chunk-CBNENUW6.js";
6
+ import "./chunk-K3NQKI34.js";
7
+
8
+ // src/attachment-cli.ts
9
+ function printHelp() {
10
+ console.log(`
11
+ workspacecord attachment \u2014 fetch registered Discord attachments
12
+
13
+ Usage:
14
+ workspacecord attachment fetch --session <session-id> --message <message-id> --attachment <attachment-id>
15
+ workspacecord attachment fetch --session <session-id> --message <message-id> --all
16
+ `);
17
+ }
18
+ function readFlag(args, flag) {
19
+ const index = args.indexOf(flag);
20
+ if (index < 0) return void 0;
21
+ return args[index + 1];
22
+ }
23
+ async function handleAttachment(args) {
24
+ const [subcommand, ...rest] = args;
25
+ switch (subcommand) {
26
+ case "fetch": {
27
+ const sessionId = readFlag(rest, "--session");
28
+ const messageId = readFlag(rest, "--message");
29
+ const attachmentId = readFlag(rest, "--attachment");
30
+ const all = rest.includes("--all");
31
+ const currentSessionId = readFlag(rest, "--current-session") ?? process.env.workspacecord_CURRENT_SESSION_ID ?? sessionId;
32
+ if (!sessionId || !messageId || !attachmentId && !all) {
33
+ console.error(
34
+ "Usage: workspacecord attachment fetch --session <session-id> --message <message-id> (--attachment <attachment-id> | --all)"
35
+ );
36
+ process.exit(1);
37
+ }
38
+ const downloaded = await fetchRegisteredAttachments({
39
+ sessionId,
40
+ messageId,
41
+ attachmentId,
42
+ all,
43
+ currentSessionId
44
+ });
45
+ console.log(JSON.stringify(downloaded, null, 2));
46
+ return;
47
+ }
48
+ case void 0:
49
+ case "help":
50
+ case "--help":
51
+ case "-h":
52
+ printHelp();
53
+ return;
54
+ default:
55
+ console.error(`Unknown attachment subcommand: ${subcommand}`);
56
+ printHelp();
57
+ process.exit(1);
58
+ }
59
+ }
60
+ export {
61
+ handleAttachment
62
+ };
@@ -30,20 +30,53 @@ var VALID_KEYS = /* @__PURE__ */ new Set([
30
30
  "ANTHROPIC_BASE_URL",
31
31
  "MESSAGE_RETENTION_DAYS",
32
32
  "RATE_LIMIT_MS",
33
+ "ACK_REACTION",
34
+ "REPLY_TO_MODE",
35
+ "TEXT_CHUNK_LIMIT",
36
+ "CHUNK_MODE",
37
+ "IPC_SOCKET_PATH",
33
38
  "SHELL_ENABLED",
34
39
  "SHELL_ALLOWED_USERS",
35
40
  "SESSION_SYNC_INTERVAL_MS",
41
+ "SESSION_SYNC_RECENT_DAYS",
36
42
  "HEALTH_REPORT_ENABLED",
37
43
  "HEALTH_REPORT_INTERVAL_MS",
38
44
  "HEALTH_CHECK_STUCK_THRESHOLD_MS",
39
- "HEALTH_CHECK_IDLE_THRESHOLD_MS"
45
+ "HEALTH_CHECK_IDLE_THRESHOLD_MS",
46
+ "HOOK_SECRET"
40
47
  ]);
41
48
  var CODEX_SANDBOX_MODES = /* @__PURE__ */ new Set(["read-only", "workspace-write", "danger-full-access"]);
42
49
  var CODEX_APPROVAL_POLICIES = /* @__PURE__ */ new Set(["never", "on-request", "on-failure", "untrusted"]);
43
50
  var store = null;
51
+ var DEFAULT_TEST_CONFIG_PATH = join(process.cwd(), ".workspacecord-config.json");
52
+ var DEFAULT_TEST_FALLBACK_PATH = join(process.cwd(), ".workspacecord-config-fallback.json");
53
+ function isTestEnvironment() {
54
+ return process.env.VITEST === "true" || process.env.NODE_ENV === "test";
55
+ }
56
+ function resolveGlobalConfigPath() {
57
+ if (process.env.WORKSPACECORD_CONFIG_PATH) {
58
+ return process.env.WORKSPACECORD_CONFIG_PATH;
59
+ }
60
+ if (isTestEnvironment()) {
61
+ return DEFAULT_TEST_CONFIG_PATH;
62
+ }
63
+ const baseDir = process.env.WORKSPACECORD_CONFIG_DIR ? process.env.WORKSPACECORD_CONFIG_DIR : join(homedir(), ".config", "workspacecord");
64
+ return join(baseDir, "config.json");
65
+ }
66
+ function createConfigStore(path) {
67
+ try {
68
+ return new Configstore("workspacecord", {}, { configPath: path });
69
+ } catch (err) {
70
+ if (!isTestEnvironment() || path === DEFAULT_TEST_CONFIG_PATH || path === DEFAULT_TEST_FALLBACK_PATH) {
71
+ throw err;
72
+ }
73
+ return new Configstore("workspacecord", {}, { configPath: DEFAULT_TEST_FALLBACK_PATH });
74
+ }
75
+ }
44
76
  function getStore() {
45
77
  if (!store) {
46
- store = new Configstore("workspacecord", {}, { globalConfigPath: true });
78
+ const path = resolveGlobalConfigPath();
79
+ store = createConfigStore(path);
47
80
  }
48
81
  return store;
49
82
  }
@@ -72,8 +105,38 @@ function validateConfigValue(key, value) {
72
105
  return `Invalid value for SHELL_ENABLED. Expected "true" or "false"`;
73
106
  }
74
107
  break;
108
+ case "TEXT_CHUNK_LIMIT": {
109
+ const n = Number(value);
110
+ if (!Number.isInteger(n) || n < 1 || n > 2e3) {
111
+ return `Invalid value for ${key}. Expected an integer between 1 and 2000`;
112
+ }
113
+ break;
114
+ }
115
+ case "ACK_REACTION":
116
+ if (value === "") break;
117
+ if (/^<a?:[A-Za-z0-9_~]+:\d+>$/.test(value)) break;
118
+ if (/\s/.test(value)) {
119
+ return "Invalid value for ACK_REACTION. Expected a Unicode emoji, empty string, or custom emoji like <:name:id>";
120
+ }
121
+ break;
122
+ case "IPC_SOCKET_PATH":
123
+ if (!value.trim()) {
124
+ return "Invalid value for IPC_SOCKET_PATH. Expected a non-empty socket path";
125
+ }
126
+ break;
127
+ case "REPLY_TO_MODE":
128
+ if (!["off", "first", "all"].includes(value)) {
129
+ return `Invalid value for REPLY_TO_MODE. Expected one of: off, first, all`;
130
+ }
131
+ break;
132
+ case "CHUNK_MODE":
133
+ if (!["length", "newline"].includes(value)) {
134
+ return `Invalid value for CHUNK_MODE. Expected one of: length, newline`;
135
+ }
136
+ break;
75
137
  case "RATE_LIMIT_MS":
76
138
  case "SESSION_SYNC_INTERVAL_MS":
139
+ case "SESSION_SYNC_RECENT_DAYS":
77
140
  case "HEALTH_REPORT_INTERVAL_MS":
78
141
  case "HEALTH_CHECK_STUCK_THRESHOLD_MS":
79
142
  case "HEALTH_CHECK_IDLE_THRESHOLD_MS": {
@@ -1,53 +1,11 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ Store
4
+ } from "./chunk-CBNENUW6.js";
2
5
 
3
6
  // src/project-registry.ts
4
7
  import { randomUUID } from "crypto";
5
8
  import { resolve } from "path";
6
-
7
- // src/persistence.ts
8
- import { readFile, writeFile, mkdir, rename } from "fs/promises";
9
- import { existsSync } from "fs";
10
- import { dirname, join } from "path";
11
- import { homedir } from "os";
12
- var dataDirOverride = null;
13
- function getDataDir() {
14
- return dataDirOverride ?? join(homedir(), ".workspacecord");
15
- }
16
- var Store = class {
17
- filename;
18
- writeQueue = Promise.resolve();
19
- constructor(filename) {
20
- this.filename = filename;
21
- }
22
- get filePath() {
23
- return join(getDataDir(), this.filename);
24
- }
25
- async read() {
26
- try {
27
- const data = await readFile(this.filePath, "utf-8");
28
- return JSON.parse(data);
29
- } catch {
30
- return null;
31
- }
32
- }
33
- async write(data) {
34
- const nextWrite = this.writeQueue.catch(() => {
35
- }).then(async () => {
36
- const filePath = this.filePath;
37
- const dir = dirname(filePath);
38
- if (!existsSync(dir)) {
39
- await mkdir(dir, { recursive: true });
40
- }
41
- const tmpPath = filePath + ".tmp";
42
- await writeFile(tmpPath, JSON.stringify(data, null, 2), "utf-8");
43
- await rename(tmpPath, filePath);
44
- });
45
- this.writeQueue = nextWrite;
46
- await nextWrite;
47
- }
48
- };
49
-
50
- // src/project-registry.ts
51
9
  var store = new Store("projects.json");
52
10
  var projects = [];
53
11
  function normalizePath(path) {
@@ -158,7 +116,6 @@ async function updateProject(project) {
158
116
  }
159
117
 
160
118
  export {
161
- Store,
162
119
  loadRegistry,
163
120
  getProjectByName,
164
121
  getProjectByPath,
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/persistence.ts
4
+ import { readFile, writeFile, mkdir, rename } from "fs/promises";
5
+ import { existsSync } from "fs";
6
+ import { dirname, join } from "path";
7
+ import { homedir } from "os";
8
+ var dataDirOverride = null;
9
+ var DEFAULT_TEST_DATA_DIR = join(process.cwd(), ".workspacecord-data");
10
+ function getDataDir() {
11
+ if (dataDirOverride) return dataDirOverride;
12
+ if (process.env.WORKSPACECORD_DATA_DIR) return process.env.WORKSPACECORD_DATA_DIR;
13
+ if (process.env.VITEST === "true" || process.env.NODE_ENV === "test") {
14
+ return DEFAULT_TEST_DATA_DIR;
15
+ }
16
+ return join(homedir(), ".workspacecord");
17
+ }
18
+ var Store = class {
19
+ filename;
20
+ writeQueue = Promise.resolve();
21
+ constructor(filename) {
22
+ this.filename = filename;
23
+ }
24
+ get filePath() {
25
+ return join(getDataDir(), this.filename);
26
+ }
27
+ async read() {
28
+ try {
29
+ const data = await readFile(this.filePath, "utf-8");
30
+ return JSON.parse(data);
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+ async write(data) {
36
+ const nextWrite = this.writeQueue.catch(() => {
37
+ }).then(async () => {
38
+ const filePath = this.filePath;
39
+ const dir = dirname(filePath);
40
+ if (!existsSync(dir)) {
41
+ await mkdir(dir, { recursive: true });
42
+ }
43
+ const tmpPath = filePath + ".tmp";
44
+ await writeFile(tmpPath, JSON.stringify(data, null, 2), "utf-8");
45
+ await rename(tmpPath, filePath);
46
+ });
47
+ this.writeQueue = nextWrite;
48
+ await nextWrite;
49
+ }
50
+ };
51
+
52
+ export {
53
+ getDataDir,
54
+ Store
55
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getConfigValue
4
- } from "./chunk-OKI4UVGY.js";
4
+ } from "./chunk-52OOARML.js";
5
5
 
6
6
  // src/config.ts
7
7
  import { homedir } from "os";
@@ -37,6 +37,11 @@ function optionalBool(key, fallback) {
37
37
  if (!value) return fallback;
38
38
  return value === "true" || value === "1";
39
39
  }
40
+ function optionalEnum(key, fallback, allowed) {
41
+ const value = getConfigValue(key);
42
+ if (!value) return fallback;
43
+ return allowed.includes(value) ? value : fallback;
44
+ }
40
45
  var config = {
41
46
  token: required("DISCORD_TOKEN"),
42
47
  clientId: required("DISCORD_CLIENT_ID"),
@@ -44,37 +49,44 @@ var config = {
44
49
  allowedUsers: optionalList("ALLOWED_USERS"),
45
50
  allowAllUsers: optionalBool("ALLOW_ALL_USERS", false),
46
51
  dataDir: join(homedir(), ".workspacecord"),
47
- defaultProvider: optional("DEFAULT_PROVIDER", "codex"),
48
- defaultMode: optional("DEFAULT_MODE", "auto"),
49
- claudePermissionMode: optional("CLAUDE_PERMISSION_MODE", "normal"),
52
+ defaultProvider: optionalEnum("DEFAULT_PROVIDER", "codex", ["claude", "codex"]),
53
+ defaultMode: optionalEnum("DEFAULT_MODE", "auto", ["auto", "plan", "normal", "monitor"]),
54
+ claudePermissionMode: optionalEnum("CLAUDE_PERMISSION_MODE", "normal", ["bypass", "normal"]),
50
55
  maxSubagentDepth: optionalInt("MAX_SUBAGENT_DEPTH", 3),
51
56
  maxActiveSessionsPerProject: optionalInt("MAX_ACTIVE_SESSIONS", 20),
52
57
  autoArchiveDays: optionalInt("AUTO_ARCHIVE_DAYS", 7),
53
58
  messageRetentionDays: optionalInt("MESSAGE_RETENTION_DAYS", 0),
54
59
  rateLimitMs: optionalInt("RATE_LIMIT_MS", 1e3),
60
+ ackReaction: optional("ACK_REACTION", "\u{1F440}"),
61
+ replyToMode: optionalEnum("REPLY_TO_MODE", "first", ["off", "first", "all"]),
62
+ textChunkLimit: Math.max(1, Math.min(optionalInt("TEXT_CHUNK_LIMIT", 2e3), 2e3)),
63
+ chunkMode: optionalEnum("CHUNK_MODE", "length", ["length", "newline"]),
64
+ socketPath: optional("IPC_SOCKET_PATH", "/tmp/workspacecord.sock"),
55
65
  shellEnabled: optionalBool("SHELL_ENABLED", false),
56
66
  shellAllowedUsers: optionalList("SHELL_ALLOWED_USERS"),
57
- codexSandboxMode: optional("CODEX_SANDBOX_MODE", "workspace-write"),
58
- codexApprovalPolicy: optional("CODEX_APPROVAL_POLICY", "on-failure"),
59
- codexNetworkAccessEnabled: optionalBool("CODEX_NETWORK_ACCESS_ENABLED", false),
60
- codexWebSearchMode: optional("CODEX_WEB_SEARCH", "disabled"),
61
- codexReasoningEffort: optional("CODEX_REASONING_EFFORT", ""),
67
+ codexSandboxMode: optionalEnum("CODEX_SANDBOX_MODE", "workspace-write", ["read-only", "workspace-write", "danger-full-access"]),
68
+ codexApprovalPolicy: optionalEnum("CODEX_APPROVAL_POLICY", "on-failure", ["never", "on-request", "on-failure", "untrusted"]),
69
+ codexNetworkAccessEnabled: optionalBool("CODEX_NETWORK_ACCESS_ENABLED", true),
70
+ codexWebSearchMode: optionalEnum("CODEX_WEB_SEARCH", "live", ["disabled", "cached", "live"]),
71
+ codexReasoningEffort: optionalEnum("CODEX_REASONING_EFFORT", "", ["", "minimal", "low", "medium", "high", "xhigh"]),
62
72
  codexBaseUrl: optional("CODEX_BASE_URL", ""),
63
73
  codexApiKey: optional("CODEX_API_KEY", ""),
64
74
  codexPath: optional("CODEX_PATH", ""),
65
75
  anthropicApiKey: optional("ANTHROPIC_API_KEY", ""),
66
76
  anthropicBaseUrl: optional("ANTHROPIC_BASE_URL", ""),
67
77
  sessionSyncIntervalMs: optionalInt("SESSION_SYNC_INTERVAL_MS", 3e4),
78
+ sessionSyncRecentDays: optionalInt("SESSION_SYNC_RECENT_DAYS", 3),
68
79
  healthReportIntervalMs: optionalInt("HEALTH_REPORT_INTERVAL_MS", 6e5),
69
80
  healthReportEnabled: optionalBool("HEALTH_REPORT_ENABLED", true),
70
81
  healthCheckStuckThresholdMs: optionalInt("HEALTH_CHECK_STUCK_THRESHOLD_MS", 18e5),
71
- healthCheckIdleThresholdMs: optionalInt("HEALTH_CHECK_IDLE_THRESHOLD_MS", 72e5)
82
+ healthCheckIdleThresholdMs: optionalInt("HEALTH_CHECK_IDLE_THRESHOLD_MS", 72e5),
83
+ hookSecret: optional("HOOK_SECRET", "")
72
84
  };
73
85
  if (config.anthropicApiKey) process.env.ANTHROPIC_API_KEY = config.anthropicApiKey;
74
86
  if (config.anthropicBaseUrl) process.env.ANTHROPIC_BASE_URL = config.anthropicBaseUrl;
75
87
  if (config.allowedUsers.length === 0 && !config.allowAllUsers) {
76
88
  if (process.env.NODE_ENV !== "test" && process.env.VITEST !== "true") {
77
- console.error("ERROR: Set ALLOWED_USERS or ALLOW_ALL_USERS=true");
89
+ console.error("ERROR: no users are allowed because neither ALLOWED_USERS nor ALLOW_ALL_USERS is configured");
78
90
  process.exit(1);
79
91
  }
80
92
  }