@slock-ai/daemon 0.52.2 → 0.53.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.
@@ -9,7 +9,7 @@ import {
9
9
  // src/core.ts
10
10
  import path16 from "path";
11
11
  import os8 from "os";
12
- import { createRequire } from "module";
12
+ import { createRequire as createRequire2 } from "module";
13
13
  import { accessSync } from "fs";
14
14
  import { fileURLToPath } from "url";
15
15
 
@@ -772,12 +772,13 @@ import os6 from "os";
772
772
 
773
773
  // src/drivers/claude.ts
774
774
  import { spawn } from "child_process";
775
- import { existsSync as existsSync3, readdirSync, readFileSync, statSync, writeFileSync as writeFileSync2 } from "fs";
775
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
776
776
  import os2 from "os";
777
777
  import path4 from "path";
778
778
 
779
779
  // src/drivers/cliTransport.ts
780
- import { mkdirSync, rmSync, writeFileSync } from "fs";
780
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
781
+ import { createRequire } from "module";
781
782
  import path2 from "path";
782
783
 
783
784
  // src/drivers/systemPrompt.ts
@@ -872,13 +873,15 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
872
873
  17. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
873
874
  18. **\`slock profile show\`** \u2014 Show your own profile, or another visible profile via \`@handle\`. Mirrors the canonical Slock profile view.
874
875
  19. **\`slock profile update\`** \u2014 Update your own profile. Supports \`--avatar-file <path>\`, \`--avatar-url pixel:random:<seed>\`, \`--display-name <name>\`, and \`--description <text>\`. Use \`--avatar-url pixel:random:<seed>\` when you want a new pixel avatar but do not have a local image file. Values must be non-empty. Provide at least one flag per call; multiple flags can be combined.
875
- 20. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
876
- 21. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
877
- 22. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
878
- 23. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
879
- 24. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
880
- 25. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
881
- 26. **\`slock action prepare\`** \u2014 Prepare an action card for a human to commit (B-mode quick-commit shortcut). Posts a card the human can click to execute the action under their own identity. Pass \`--target <ch>\` and pipe the action JSON on stdin (variants: \`channel:create\`, \`agent:create\`).
876
+ 20. **\`slock integration list\`** \u2014 List registered third-party services and this agent's active Slock Agent Logins.
877
+ 21. **\`slock integration login\`** \u2014 Provision or reuse this agent's login for a registered third-party service.
878
+ 22. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
879
+ 23. **\`slock reminder list\`** \u2014 List your reminders, including lifecycle history for each reminder.
880
+ 24. **\`slock reminder snooze\`** \u2014 Push a reminder later without replacing it.
881
+ 25. **\`slock reminder update\`** \u2014 Change a reminder's title, schedule, or recurrence without creating a new reminder.
882
+ 26. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
883
+ 27. **\`slock reminder log\`** \u2014 Show the event log for a reminder, including fires, dismissals, and reschedules.
884
+ 28. **\`slock action prepare\`** \u2014 Prepare an action card for a human to commit (B-mode quick-commit shortcut). Posts a card the human can click to execute the action under their own identity. Pass \`--target <ch>\` and pipe the action JSON on stdin (variants: \`channel:create\`, \`agent:create\`).
882
885
 
883
886
  The CLI prints human-readable canonical text on success (matching the format you see in received messages and history). On failure it prints JSON to stderr:
884
887
  - failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
@@ -913,20 +916,23 @@ Use reminders for follow-up that depends on future state you cannot resolve now,
913
916
  When a reminder already exists, prefer \`slock reminder snooze\` to push it later, \`slock reminder update\` to change its meaning or schedule, and \`slock reminder cancel\` only when it is truly no longer needed.
914
917
  Use ${scheduleReminderCmd} rather than runtime-native wake or cron tools such as ScheduleWakeup or CronCreate for user-visible reminders, so reminders stay author-owned, persistent, observable, snoozable, updatable, and cancelable in Slock.
915
918
  Create agent reminders only after resolving the anchor message from the current conversation and passing its msgId explicitly; if no anchor can be resolved, consider posting a status update in the relevant thread so the intent is visible, then revisit when context is available.`;
919
+ const messageHeredocDelimiter = "SLOCKMSG";
916
920
  const sendingMessagesSection = isCli ? `### Sending messages
917
921
 
918
- - **Reply to a channel**: \`slock message send --target "#channel-name" <<'EOF'\` followed by the message body and \`EOF\`
919
- - **Reply to a DM**: \`slock message send --target dm:@peer-name <<'EOF'\` followed by the message body and \`EOF\`
920
- - **Reply in a thread**: \`slock message send --target "#channel:shortid" <<'EOF'\` followed by the message body and \`EOF\`
921
- - **Start a NEW DM**: \`slock message send --target dm:@person-name <<'EOF'\` followed by the message body and \`EOF\`
922
+ - **Reply to a channel**: \`slock message send --target "#channel-name" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
923
+ - **Reply to a DM**: \`slock message send --target dm:@peer-name <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
924
+ - **Reply in a thread**: \`slock message send --target "#channel:shortid" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
925
+ - **Start a NEW DM**: \`slock message send --target dm:@person-name <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
922
926
 
923
927
  Message content is always read from stdin. Use a heredoc so quotes, backticks, code blocks, and newlines are not interpreted by the shell:
924
928
  \`\`\`bash
925
- slock message send --target "#channel-name" <<'EOF'
929
+ slock message send --target "#channel-name" <<'${messageHeredocDelimiter}'
926
930
  Long message with "quotes", $vars, \`backticks\`, and code blocks.
927
- EOF
931
+ ${messageHeredocDelimiter}
928
932
  \`\`\`
929
933
 
934
+ Use a delimiter that is unlikely to appear in the message body; the examples use \`${messageHeredocDelimiter}\` instead of \`EOF\` so shell snippets and recovery drafts are less likely to leak delimiter text into sent messages.
935
+
930
936
  If Slock says a message was not sent and was saved as a draft, choose one path:
931
937
  - To update the draft, use a normal \`slock message send --target <target>\` with the revised content.
932
938
  - To send the current draft unchanged, use \`slock message send --send-draft --target <target>\` with no stdin. Do not use \`--send-draft\` when changing content.
@@ -945,7 +951,7 @@ Threads are sub-conversations attached to a specific message. They let you discu
945
951
 
946
952
  - **Thread targets** have a colon and short ID suffix: \`#general:a1b2c3d4\` (thread in #general) or \`dm:@richard:x9y8z7a0\` (thread in a DM).
947
953
  - When you receive a message from a thread (the target has a \`:shortid\` suffix), **always reply using that same target** to keep the conversation in the thread.
948
- - **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=a1b2c3d4 ...]\`, reply with \`slock message send --target "#general:a1b2c3d4" <<'EOF'\` followed by the message body and \`EOF\`. The thread will be auto-created if it doesn't exist yet.
954
+ - **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=a1b2c3d4 ...]\`, reply with \`slock message send --target "#general:a1b2c3d4" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`. The thread will be auto-created if it doesn't exist yet.
949
955
  - When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
950
956
  - You can read thread history: \`slock message read --channel "#general:a1b2c3d4"\`
951
957
  - You can stop receiving ordinary delivery for a thread with \`slock thread unfollow --target "#general:a1b2c3d4"\`. Only do this when your work in that thread is clearly complete or no longer relevant.
@@ -979,6 +985,11 @@ Each channel has a **name** and optionally a **description** that define its pur
979
985
  - **Reply in context** \u2014 always respond in the channel/thread the message came from.
980
986
  - **Stay on topic** \u2014 when proactively sharing results or updates, post in the channel most relevant to the work. Don't scatter messages across unrelated channels.
981
987
  - If unsure where something belongs, call ${serverInfoCmd} to review channel descriptions.`;
988
+ const thirdPartyIntegrationsSection = isCli ? `### Third-party integrations
989
+
990
+ If a registered third-party service requires login, use Slock Agent Login through the CLI instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app, first run \`slock integration list\` and match the app to a registered service before browsing the app. Use \`slock integration login --service <service>\` to provision or reuse your agent login for that service. If the CLI reports that the \`integration\` command is unknown, the local daemon/CLI is too old for Slock Agent Login; report that the machine must be upgraded/restarted instead of calling internal HTTP endpoints yourself. When the command returns \`Agent login ready\` or \`Already logged in\`, the agent-side login is ready. If the output includes an app URL, open that URL as the service-provided third-party app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow, use internal request IDs as OAuth callback codes, call internal Slock integration endpoints directly, or call third-party exchange endpoints unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use \`slock profile show\`. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the registered service / Slock Agent Login path.` : `### Third-party integrations
991
+
992
+ If a registered third-party service requires login, use Slock Agent Login through the available registered-service interface instead of asking the human to copy tokens or complete human OAuth for you. If a human asks you to sign into, open, use, or fetch identity from a third-party app, first inspect the registered-service interface and match the app to a registered service before browsing the app. Once the registered-service interface reports the agent login is ready, the agent-side login is ready. If that interface provides an app URL, use it as the service-provided third-party app surface; it should look like the service's normal Login with Slock callback and not require you to understand Slock's internal grant/request protocol. Do not crawl third-party routes looking for a session before trying the registered-service login path. Do not open the human \`Login with Slock\` browser flow or treat internal request IDs as OAuth callback codes unless a human explicitly asks you to debug that server-to-server protocol. If the service or human asks for your Slock Agent identity card, use your Slock profile view. Third-party pages may show \`Login with Slock\`; for agent-facing access, prefer the registered service / Slock Agent Login path.`;
982
993
  const readingHistorySection = isCli ? `### Reading history
983
994
 
984
995
  \`slock message read --channel "#channel-name"\` or \`slock message read --channel dm:@peer-name\` or \`slock message read --channel "#channel:shortid"\`
@@ -1015,7 +1026,7 @@ Only top-level channel / DM messages can become tasks. Messages inside threads a
1015
1026
  **Workflow:**
1016
1027
  1. Receive a message that requires action \u2192 claim it first (by task number if already a task, or by message ID if it's a regular message)
1017
1028
  2. If the claim fails, someone else is working on it \u2014 move on to another task
1018
- 3. Post updates in the task's thread: \`slock message send --target "#channel:msgShortId" <<'EOF'\` followed by the message body and \`EOF\`
1029
+ 3. Post updates in the task's thread: \`slock message send --target "#channel:msgShortId" <<'${messageHeredocDelimiter}'\` followed by the message body and \`${messageHeredocDelimiter}\`
1019
1030
  4. When done, set status to \`in_review\` so a human can validate via \`slock task update\`
1020
1031
  5. After approval (e.g. "looks good", "merge it"), set status to \`done\`
1021
1032
 
@@ -1150,6 +1161,8 @@ ${discoverySection}
1150
1161
 
1151
1162
  ${channelAwarenessSection}
1152
1163
 
1164
+ ${thirdPartyIntegrationsSection}
1165
+
1153
1166
  ${readingHistorySection}
1154
1167
 
1155
1168
  ${historicalReferenceSection}
@@ -1872,8 +1885,51 @@ function unregisterAgentCredentialProxyForLaunch(input) {
1872
1885
 
1873
1886
  // src/drivers/cliTransport.ts
1874
1887
  var shellSingleQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
1888
+ var powershellSingleQuote = (value) => `'${value.replace(/'/g, "''")}'`;
1875
1889
  var DEFAULT_ACTIVE_CAPABILITIES = "send,read,mentions,tasks,reactions,server,channels";
1876
1890
  var safePathPart = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_");
1891
+ var cachedOpencliBinPath;
1892
+ function resolveOpencliBinPath() {
1893
+ if (cachedOpencliBinPath !== void 0) return cachedOpencliBinPath;
1894
+ try {
1895
+ const require2 = createRequire(import.meta.url);
1896
+ const mainPath = require2.resolve("@jackwener/opencli");
1897
+ let dir = path2.dirname(mainPath);
1898
+ const root = path2.parse(dir).root;
1899
+ while (dir && dir !== root) {
1900
+ const candidate = path2.join(dir, "package.json");
1901
+ try {
1902
+ const pkg = JSON.parse(readFileSync(candidate, "utf8"));
1903
+ if (pkg.name === "@jackwener/opencli") {
1904
+ const binEntry = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.opencli;
1905
+ if (!binEntry) {
1906
+ cachedOpencliBinPath = null;
1907
+ return null;
1908
+ }
1909
+ cachedOpencliBinPath = path2.resolve(dir, binEntry);
1910
+ return cachedOpencliBinPath;
1911
+ }
1912
+ } catch {
1913
+ }
1914
+ const parent = path2.dirname(dir);
1915
+ if (parent === dir) break;
1916
+ dir = parent;
1917
+ }
1918
+ cachedOpencliBinPath = null;
1919
+ return null;
1920
+ } catch {
1921
+ cachedOpencliBinPath = null;
1922
+ return null;
1923
+ }
1924
+ }
1925
+ function windowsUtf8Env() {
1926
+ return {
1927
+ PYTHONIOENCODING: "utf-8",
1928
+ PYTHONUTF8: "1",
1929
+ LANG: "C.UTF-8",
1930
+ LC_ALL: "C.UTF-8"
1931
+ };
1932
+ }
1877
1933
  function runtimeContextEnv(config) {
1878
1934
  const ctx = config.runtimeContext;
1879
1935
  if (!ctx) return {};
@@ -1932,10 +1988,70 @@ ${posixCredentialPrefix}exec ${shellSingleQuote(process.execPath)} ${shellSingle
1932
1988
  set "SLOCK_AGENT_PROXY_TOKEN_FILE=${agentCredentialProxyTokenFile}"\r
1933
1989
  set "SLOCK_AGENT_ACTIVE_CAPABILITIES=${DEFAULT_ACTIVE_CAPABILITIES}"\r
1934
1990
  ` : "";
1935
- const cmdBody = `@echo off\r
1936
- ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
1937
- `;
1991
+ const cmdBody = [
1992
+ "@echo off",
1993
+ "set PYTHONIOENCODING=utf-8",
1994
+ "set PYTHONUTF8=1",
1995
+ "set LANG=C.UTF-8",
1996
+ "set LC_ALL=C.UTF-8",
1997
+ "chcp 65001 >NUL 2>NUL",
1998
+ cmdCredentialLine.trimEnd(),
1999
+ `"${process.execPath}" "${ctx.slockCliPath}" %*`,
2000
+ ""
2001
+ ].filter((line) => line.length > 0).join("\r\n") + "\r\n";
1938
2002
  writeFileSync(cmdWrapper, cmdBody);
2003
+ const psWrapper = path2.join(slockDir, "slock.ps1");
2004
+ const psCredentialLines = agentCredentialProxy ? [
2005
+ `$env:SLOCK_AGENT_PROXY_URL=${powershellSingleQuote(agentCredentialProxy.proxyUrl)}`,
2006
+ `$env:SLOCK_AGENT_PROXY_TOKEN_FILE=${powershellSingleQuote(agentCredentialProxyTokenFile)}`,
2007
+ `$env:SLOCK_AGENT_ACTIVE_CAPABILITIES=${powershellSingleQuote(DEFAULT_ACTIVE_CAPABILITIES)}`
2008
+ ] : [];
2009
+ const psBody = [
2010
+ "$ErrorActionPreference = 'Stop'",
2011
+ "$utf8NoBom = [System.Text.UTF8Encoding]::new($false)",
2012
+ "[Console]::OutputEncoding = $utf8NoBom",
2013
+ "$OutputEncoding = $utf8NoBom",
2014
+ "$env:PYTHONIOENCODING = 'utf-8'",
2015
+ "$env:PYTHONUTF8 = '1'",
2016
+ "$env:LANG = 'C.UTF-8'",
2017
+ "$env:LC_ALL = 'C.UTF-8'",
2018
+ ...psCredentialLines,
2019
+ `$node = ${powershellSingleQuote(process.execPath)}`,
2020
+ `$cli = ${powershellSingleQuote(ctx.slockCliPath)}`,
2021
+ "if ($MyInvocation.ExpectingInput) {",
2022
+ " $input | & $node $cli @args",
2023
+ "} else {",
2024
+ " & $node $cli @args",
2025
+ "}",
2026
+ "exit $LASTEXITCODE",
2027
+ ""
2028
+ ].join("\r\n");
2029
+ writeFileSync(psWrapper, psBody);
2030
+ }
2031
+ const opencliBinPath = resolveOpencliBinPath();
2032
+ if (opencliBinPath) {
2033
+ const opencliPosixWrapper = path2.join(slockDir, "opencli");
2034
+ writeFileSync(
2035
+ opencliPosixWrapper,
2036
+ `#!/usr/bin/env bash
2037
+ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(opencliBinPath)} "$@"
2038
+ `,
2039
+ { mode: 493 }
2040
+ );
2041
+ if (platform === "win32") {
2042
+ const opencliCmdWrapper = path2.join(slockDir, "opencli.cmd");
2043
+ const opencliCmdBody = [
2044
+ "@echo off",
2045
+ "set PYTHONIOENCODING=utf-8",
2046
+ "set PYTHONUTF8=1",
2047
+ "set LANG=C.UTF-8",
2048
+ "set LC_ALL=C.UTF-8",
2049
+ "chcp 65001 >NUL 2>NUL",
2050
+ `"${process.execPath}" "${opencliBinPath}" %*`,
2051
+ ""
2052
+ ].join("\r\n") + "\r\n";
2053
+ writeFileSync(opencliCmdWrapper, opencliCmdBody);
2054
+ }
1939
2055
  }
1940
2056
  const wrapperPath = platform === "win32" ? path2.join(slockDir, "slock.cmd") : posixWrapper;
1941
2057
  const spawnEnv = {
@@ -1943,6 +2059,7 @@ ${cmdCredentialLine}"${process.execPath}" "${ctx.slockCliPath}" %*\r
1943
2059
  FORCE_COLOR: "0",
1944
2060
  ...ctx.config.envVars || {},
1945
2061
  ...extraEnv,
2062
+ ...platform === "win32" ? windowsUtf8Env() : {},
1946
2063
  ...runtimeContextEnv(ctx.config),
1947
2064
  [SLOCK_HOME_ENV]: slockHome,
1948
2065
  SLOCK_AGENT_ID: ctx.agentId,
@@ -2085,7 +2202,7 @@ function expandClaudeMcpConfigVariables(raw, vars) {
2085
2202
  function readClaudeMcpServers(configPath, vars = {}) {
2086
2203
  try {
2087
2204
  const parsed = JSON.parse(
2088
- expandClaudeMcpConfigVariables(readFileSync(configPath, "utf8"), vars)
2205
+ expandClaudeMcpConfigVariables(readFileSync2(configPath, "utf8"), vars)
2089
2206
  );
2090
2207
  if (!isRecord(parsed) || !isRecord(parsed.mcpServers)) return null;
2091
2208
  return parsed.mcpServers;
@@ -2387,7 +2504,7 @@ var ClaudeDriver = class {
2387
2504
 
2388
2505
  // src/drivers/codex.ts
2389
2506
  import { spawn as spawn2, execFileSync as execFileSync2, execSync } from "child_process";
2390
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
2507
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
2391
2508
  import os3 from "os";
2392
2509
  import path5 from "path";
2393
2510
  function getCodexNotificationErrorMessage(params) {
@@ -2925,7 +3042,7 @@ function detectCodexModels(home = os3.homedir()) {
2925
3042
  const configPath = path5.join(home, ".codex", "config.toml");
2926
3043
  let models = [];
2927
3044
  try {
2928
- const raw = readFileSync2(cachePath, "utf8");
3045
+ const raw = readFileSync3(cachePath, "utf8");
2929
3046
  const parsed = JSON.parse(raw);
2930
3047
  const entries = Array.isArray(parsed?.models) ? parsed.models : [];
2931
3048
  for (const entry of entries) {
@@ -2942,7 +3059,7 @@ function detectCodexModels(home = os3.homedir()) {
2942
3059
  if (models.length === 0) return null;
2943
3060
  let defaultModel;
2944
3061
  try {
2945
- const raw = readFileSync2(configPath, "utf8");
3062
+ const raw = readFileSync3(configPath, "utf8");
2946
3063
  const match = raw.match(/^\s*model\s*=\s*"([^"]+)"/m);
2947
3064
  if (match) defaultModel = match[1];
2948
3065
  } catch {
@@ -3540,7 +3657,7 @@ var GeminiDriver = class {
3540
3657
  // src/drivers/kimi.ts
3541
3658
  import { randomUUID } from "crypto";
3542
3659
  import { spawn as spawn6 } from "child_process";
3543
- import { existsSync as existsSync7, readFileSync as readFileSync3, writeFileSync as writeFileSync6 } from "fs";
3660
+ import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync6 } from "fs";
3544
3661
  import os4 from "os";
3545
3662
  import path9 from "path";
3546
3663
  var KIMI_WIRE_PROTOCOL_VERSION = "1.3";
@@ -3775,7 +3892,7 @@ function detectKimiModels(home = os4.homedir()) {
3775
3892
  const configPath = path9.join(home, ".kimi", "config.toml");
3776
3893
  let raw;
3777
3894
  try {
3778
- raw = readFileSync3(configPath, "utf8");
3895
+ raw = readFileSync4(configPath, "utf8");
3779
3896
  } catch {
3780
3897
  return null;
3781
3898
  }
@@ -3799,7 +3916,7 @@ function detectKimiModels(home = os4.homedir()) {
3799
3916
 
3800
3917
  // src/drivers/opencode.ts
3801
3918
  import { spawn as spawn7, spawnSync as spawnSync2 } from "child_process";
3802
- import { existsSync as existsSync8, readFileSync as readFileSync4 } from "fs";
3919
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
3803
3920
  import os5 from "os";
3804
3921
  import path10 from "path";
3805
3922
  var CHAT_MCP_SERVER_NAME = "chat";
@@ -3851,7 +3968,7 @@ function parseUserOpenCodeConfig(ctx) {
3851
3968
  function readLocalOpenCodeConfig(home = os5.homedir()) {
3852
3969
  const configPath = path10.join(home, ".config", "opencode", "opencode.json");
3853
3970
  try {
3854
- return parseOpenCodeConfigContent(readFileSync4(configPath, "utf8"));
3971
+ return parseOpenCodeConfigContent(readFileSync5(configPath, "utf8"));
3855
3972
  } catch {
3856
3973
  }
3857
3974
  return {};
@@ -4102,7 +4219,7 @@ function resolveWindowsOpenCodePackageEntry(commandPath, deps = {}) {
4102
4219
  }
4103
4220
  function extractWindowsShimTargets(commandPath, deps = {}) {
4104
4221
  if (!isWindowsCommandShim(commandPath)) return [];
4105
- const readFileSyncFn = deps.readFileSyncFn ?? readFileSync4;
4222
+ const readFileSyncFn = deps.readFileSyncFn ?? readFileSync5;
4106
4223
  const commandDir = path10.win32.dirname(commandPath);
4107
4224
  let raw;
4108
4225
  try {
@@ -5158,7 +5275,7 @@ Do not copy these answers verbatim.
5158
5275
  ## FAQ 15: How do I create agents or channels?
5159
5276
  ### Answer idea
5160
5277
  - When the owner agrees to a new agent or channel, **post an action card** with \`slock action prepare\`. The card lives inline in chat; the owner clicks the action button, the matching create dialog opens prefilled with your values (editable), and the resource is created under their identity when they submit.
5161
- - v1 supports three action types via \`slock action prepare --target '<channel>' <<EOF { ... } EOF\`:
5278
+ - v1 supports three action types via \`slock action prepare --target '<channel>' <<'SLOCKACTION' { ... } SLOCKACTION\`:
5162
5279
  - \`{type: "channel:create", name, visibility: "public" | "private", description?, initialHumans?: ["@alice"], initialAgents?: ["@scout"], draftHint?}\`
5163
5280
  - \`{type: "agent:create", name, description?, draftHint?}\` \u2014 runtime / model / computer are the owner's call, not yours
5164
5281
  - \`{type: "channel:add_member", channel: "#existing-channel", humans?: ["@alice"], agents?: ["@scout"], draftHint?}\` \u2014 at least one of humans / agents must be non-empty. The owner clicks "Add Members" on the card; an AddMembers dialog opens with your suggested list (each row toggleable) and the owner submits to actually add them.
@@ -8582,7 +8699,7 @@ var ReminderCache = class {
8582
8699
 
8583
8700
  // src/machineLock.ts
8584
8701
  import { createHash as createHash3, randomUUID as randomUUID2 } from "crypto";
8585
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
8702
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync8 } from "fs";
8586
8703
  import os7 from "os";
8587
8704
  import path13 from "path";
8588
8705
  var INCOMPLETE_LOCK_STALE_MS = 3e4;
@@ -8610,7 +8727,7 @@ function ownerPath(lockDir) {
8610
8727
  }
8611
8728
  function readOwner(lockDir) {
8612
8729
  try {
8613
- return JSON.parse(readFileSync5(ownerPath(lockDir), "utf8"));
8730
+ return JSON.parse(readFileSync6(ownerPath(lockDir), "utf8"));
8614
8731
  } catch {
8615
8732
  return null;
8616
8733
  }
@@ -9253,7 +9370,7 @@ function parseDaemonCliArgs(args) {
9253
9370
  }
9254
9371
  function readDaemonVersion(moduleUrl = import.meta.url) {
9255
9372
  try {
9256
- const require2 = createRequire(moduleUrl);
9373
+ const require2 = createRequire2(moduleUrl);
9257
9374
  return require2("../package.json").version;
9258
9375
  } catch {
9259
9376
  return "0.0.0-dev";
package/dist/cli/index.js CHANGED
@@ -14281,6 +14281,7 @@ var ApiClient = class {
14281
14281
  if (suffix.startsWith("/search")) return `/internal/agent-api/search${suffix.slice("/search".length)}`;
14282
14282
  if (suffix.startsWith("/channel-members")) return `/internal/agent-api/channel-members${suffix.slice("/channel-members".length)}`;
14283
14283
  if (suffix === "/profile" || suffix.startsWith("/profile/")) return `/internal/agent-api${suffix}`;
14284
+ if (suffix === "/integrations" || suffix.startsWith("/integrations/")) return `/internal/agent-api${suffix}`;
14284
14285
  if (suffix === "/upload") return "/internal/agent-api/upload";
14285
14286
  if (suffix === "/resolve-channel") return "/internal/agent-api/resolve-channel";
14286
14287
  if (suffix === "/threads/unfollow") return "/internal/agent-api/threads/unfollow";
@@ -14432,6 +14433,7 @@ var ApiClient = class {
14432
14433
  };
14433
14434
 
14434
14435
  // src/commands/action/prepare.ts
14436
+ var ACTION_HEREDOC_DELIMITER = "SLOCKACTION";
14435
14437
  var PrepareActionInputError = class extends Error {
14436
14438
  constructor(code, message) {
14437
14439
  super(message);
@@ -14451,9 +14453,9 @@ function missingActionMessage() {
14451
14453
  return [
14452
14454
  "No action JSON received on stdin.",
14453
14455
  "Pipe a JSON ActionCardAction object (channel:create / agent:create / channel:add_member) into slock action prepare:",
14454
- ` slock action prepare --target "#channel" <<'EOF'`,
14456
+ ` slock action prepare --target "#channel" <<'${ACTION_HEREDOC_DELIMITER}'`,
14455
14457
  ' {"type":"channel:create","name":"demo","visibility":"public"}',
14456
- " EOF"
14458
+ ` ${ACTION_HEREDOC_DELIMITER}`
14457
14459
  ].join("\n");
14458
14460
  }
14459
14461
  async function resolveActionInput(input = process.stdin) {
@@ -14573,7 +14575,7 @@ function formatServerInfo(data) {
14573
14575
  text += " (none)\n";
14574
14576
  }
14575
14577
  text += "\n### Humans\n";
14576
- text += `To start a new DM: slock message send --target "dm:@name" <<'EOF' followed by the message body and EOF. To reply in an existing DM: reuse the target from received messages.
14578
+ text += `To start a new DM: slock message send --target "dm:@name" <<'SLOCKMSG' followed by the message body and SLOCKMSG. To reply in an existing DM: reuse the target from received messages.
14577
14579
  `;
14578
14580
  if (humans.length > 0) {
14579
14581
  for (const u of humans) {
@@ -15063,6 +15065,7 @@ function clearSavedDraft(agentId, target) {
15063
15065
  }
15064
15066
 
15065
15067
  // src/commands/message/send.ts
15068
+ var MESSAGE_HEREDOC_DELIMITER = "SLOCKMSG";
15066
15069
  var SendContentError = class extends Error {
15067
15070
  constructor(code, message) {
15068
15071
  super(message);
@@ -15082,9 +15085,9 @@ function missingContentMessage() {
15082
15085
  return [
15083
15086
  "No message content received on stdin.",
15084
15087
  "Use a heredoc or pipe content into slock message send:",
15085
- ` slock message send --target "#channel" <<'EOF'`,
15088
+ ` slock message send --target "#channel" <<'${MESSAGE_HEREDOC_DELIMITER}'`,
15086
15089
  " message body",
15087
- " EOF"
15090
+ ` ${MESSAGE_HEREDOC_DELIMITER}`
15088
15091
  ].join("\n");
15089
15092
  }
15090
15093
  async function resolveSendContent(input = process.stdin) {
@@ -15111,9 +15114,9 @@ function rejectArgContent(positionalContent, opts) {
15111
15114
  [
15112
15115
  "Message content must be provided on stdin, not as positional arguments.",
15113
15116
  "Use:",
15114
- ` slock message send --target "#channel" <<'EOF'`,
15117
+ ` slock message send --target "#channel" <<'${MESSAGE_HEREDOC_DELIMITER}'`,
15115
15118
  " message body",
15116
- " EOF"
15119
+ ` ${MESSAGE_HEREDOC_DELIMITER}`
15117
15120
  ].join("\n")
15118
15121
  );
15119
15122
  }
@@ -15149,9 +15152,9 @@ function rejectSendDraftStdin(content, target) {
15149
15152
  [
15150
15153
  "--send-draft sends the current saved draft and does not accept stdin.",
15151
15154
  "To update the draft, send the revised content normally without --send-draft:",
15152
- ` slock message send --target "${target}" <<'EOF'`,
15155
+ ` slock message send --target "${target}" <<'${MESSAGE_HEREDOC_DELIMITER}'`,
15153
15156
  " revised message",
15154
- " EOF"
15157
+ ` ${MESSAGE_HEREDOC_DELIMITER}`
15155
15158
  ].join("\n")
15156
15159
  );
15157
15160
  }
@@ -15159,9 +15162,9 @@ function formatHeldSendOutput(target, data) {
15159
15162
  return formatFreshnessHoldOutput(target, data, {
15160
15163
  heldAction: "Your message has been saved as a draft.",
15161
15164
  draftInstructions: `To update the draft, send revised content normally:
15162
- slock message send --target "${target}" <<'EOF'
15165
+ slock message send --target "${target}" <<'${MESSAGE_HEREDOC_DELIMITER}'
15163
15166
  revised message
15164
- EOF
15167
+ ${MESSAGE_HEREDOC_DELIMITER}
15165
15168
  To send the current draft unchanged:
15166
15169
  slock message send --send-draft --target "${target}"
15167
15170
  `,
@@ -15216,9 +15219,9 @@ function registerSendCommand(parent) {
15216
15219
  [
15217
15220
  "No saved draft exists for this target.",
15218
15221
  "To create or update a draft, send message content normally:",
15219
- ` slock message send --target "${opts.target}" <<'EOF'`,
15222
+ ` slock message send --target "${opts.target}" <<'${MESSAGE_HEREDOC_DELIMITER}'`,
15220
15223
  " message body",
15221
- " EOF"
15224
+ ` ${MESSAGE_HEREDOC_DELIMITER}`
15222
15225
  ].join("\n")
15223
15226
  );
15224
15227
  return;
@@ -16170,6 +16173,153 @@ function registerProfileUpdateCommand(parent) {
16170
16173
  });
16171
16174
  }
16172
16175
 
16176
+ // src/commands/integration/_format.ts
16177
+ function formatMaybe(value) {
16178
+ return value?.trim() || "-";
16179
+ }
16180
+ function buildAgentAppUrl(returnUrl, requestId) {
16181
+ if (!returnUrl?.trim()) return null;
16182
+ try {
16183
+ const url2 = new URL(returnUrl);
16184
+ url2.searchParams.set("code", requestId);
16185
+ return url2.toString();
16186
+ } catch {
16187
+ return null;
16188
+ }
16189
+ }
16190
+ function formatIntegrationList(data) {
16191
+ const activeByServiceId = new Map(data.activeLogins.map((login) => [login.serviceId, login]));
16192
+ const lines = [];
16193
+ lines.push("Registered services:");
16194
+ if (data.services.length === 0) {
16195
+ lines.push("- none");
16196
+ } else {
16197
+ for (const service of data.services) {
16198
+ const active = activeByServiceId.get(service.id);
16199
+ lines.push(`- ${service.name}`);
16200
+ lines.push(` service: ${service.clientId}`);
16201
+ lines.push(` id: ${service.id}`);
16202
+ lines.push(` status: ${active ? "active login" : "not logged in"}`);
16203
+ lines.push(` return URL: ${formatMaybe(service.returnUrl)}`);
16204
+ if (service.homepageUrl) lines.push(` homepage: ${service.homepageUrl}`);
16205
+ if (service.description) lines.push(` description: ${service.description}`);
16206
+ if (!active) lines.push(` next: slock integration login --service ${JSON.stringify(service.clientId)}`);
16207
+ }
16208
+ }
16209
+ lines.push("");
16210
+ lines.push("Active agent logins:");
16211
+ if (data.activeLogins.length === 0) {
16212
+ lines.push("- none");
16213
+ } else {
16214
+ for (const login of data.activeLogins) {
16215
+ lines.push(`- ${login.name}`);
16216
+ lines.push(` service: ${login.clientId}`);
16217
+ lines.push(` grant id: ${login.id}`);
16218
+ lines.push(` scopes: ${login.scopes.length > 0 ? login.scopes.join(", ") : "-"}`);
16219
+ lines.push(` return URL: ${formatMaybe(login.returnUrl)}`);
16220
+ lines.push(` created: ${login.createdAt}`);
16221
+ }
16222
+ }
16223
+ return lines.join("\n");
16224
+ }
16225
+ function formatIntegrationLogin(data) {
16226
+ const verb = data.status === "already_logged_in" ? "Already logged in" : "Agent login ready";
16227
+ const lines = [
16228
+ `${verb}: ${data.service.name}`,
16229
+ `service: ${data.service.clientId}`,
16230
+ `id: ${data.service.id}`,
16231
+ `scopes: ${data.scopes.length > 0 ? data.scopes.join(", ") : "-"}`,
16232
+ `return URL: ${formatMaybe(data.service.returnUrl)}`,
16233
+ "complete: this agent login is configured in Slock; no human OAuth is required",
16234
+ "identity: run `slock profile show` if the service or human asks for your Slock Agent identity card"
16235
+ ];
16236
+ const agentAppUrl = buildAgentAppUrl(data.service.returnUrl, data.requestId);
16237
+ if (agentAppUrl) {
16238
+ lines.push(`app URL: ${agentAppUrl}`);
16239
+ lines.push("next: open the app URL if you need the third-party app surface, or run `slock integration list` to confirm active login");
16240
+ } else {
16241
+ lines.push("next: use the registered service, or run `slock integration list` to confirm active login");
16242
+ }
16243
+ return lines.join("\n");
16244
+ }
16245
+
16246
+ // src/commands/integration/list.ts
16247
+ function registerIntegrationListCommand(parent) {
16248
+ parent.command("list").description("List registered third-party services and this agent's active logins").option("--json", "Emit machine-readable JSON").action(async (opts) => {
16249
+ let ctx;
16250
+ try {
16251
+ ctx = loadAgentContext();
16252
+ } catch (err) {
16253
+ if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16254
+ throw err;
16255
+ }
16256
+ const client = new ApiClient(ctx);
16257
+ const res = await client.request(
16258
+ "GET",
16259
+ `/internal/agent/${encodeURIComponent(ctx.agentId)}/integrations`
16260
+ );
16261
+ if (!res.ok || !res.data) {
16262
+ const code = res.status >= 500 ? "SERVER_5XX" : "INTEGRATION_LIST_FAILED";
16263
+ fail(code, res.error ?? `HTTP ${res.status}`);
16264
+ }
16265
+ if (opts.json) {
16266
+ emit({ ok: true, data: res.data });
16267
+ return;
16268
+ }
16269
+ process.stdout.write(`${formatIntegrationList(res.data)}
16270
+ `);
16271
+ });
16272
+ }
16273
+
16274
+ // src/commands/integration/login.ts
16275
+ function normalizeScopes(raw) {
16276
+ if (!raw || raw.length === 0) return void 0;
16277
+ const scopes = Array.from(new Set(
16278
+ raw.flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean)
16279
+ )).sort();
16280
+ if (scopes.length === 0) {
16281
+ fail("INVALID_ARG", "--scope must include at least one non-empty scope");
16282
+ }
16283
+ return scopes;
16284
+ }
16285
+ function registerIntegrationLoginCommand(parent) {
16286
+ parent.command("login").description("Provision or reuse this agent's login for a registered service").requiredOption("--service <id>", "Registered service id, client id, or exact service name").option("--scope <scope>", "Requested scope; can be repeated or comma-separated", (value, previous = []) => {
16287
+ previous.push(value);
16288
+ return previous;
16289
+ }).option("--json", "Emit machine-readable JSON").action(async (opts) => {
16290
+ let ctx;
16291
+ try {
16292
+ ctx = loadAgentContext();
16293
+ } catch (err) {
16294
+ if (err instanceof AgentBootstrapError) fail(err.code, err.message);
16295
+ throw err;
16296
+ }
16297
+ const service = opts.service.trim();
16298
+ if (!service) {
16299
+ fail("INVALID_ARG", "--service must not be empty");
16300
+ }
16301
+ const client = new ApiClient(ctx);
16302
+ const res = await client.request(
16303
+ "POST",
16304
+ `/internal/agent/${encodeURIComponent(ctx.agentId)}/integrations/login`,
16305
+ {
16306
+ service,
16307
+ scopes: normalizeScopes(opts.scope)
16308
+ }
16309
+ );
16310
+ if (!res.ok || !res.data) {
16311
+ const code = res.status >= 500 ? "SERVER_5XX" : "INTEGRATION_LOGIN_FAILED";
16312
+ fail(code, res.error ?? `HTTP ${res.status}`);
16313
+ }
16314
+ if (opts.json) {
16315
+ emit({ ok: true, data: res.data });
16316
+ return;
16317
+ }
16318
+ process.stdout.write(`${formatIntegrationLogin(res.data)}
16319
+ `);
16320
+ });
16321
+ }
16322
+
16173
16323
  // src/commands/reminder/_format.ts
16174
16324
  var MODIFY_HINT = "(to modify: snooze/update/cancel; slock reminder --help)";
16175
16325
  function formatReminder(r) {
@@ -16562,6 +16712,9 @@ registerTaskUpdateCommand(taskCmd);
16562
16712
  var profileCmd = program.command("profile").description("Profile operations");
16563
16713
  registerProfileShowCommand(profileCmd);
16564
16714
  registerProfileUpdateCommand(profileCmd);
16715
+ var integrationCmd = program.command("integration").description("Third-party service integration operations");
16716
+ registerIntegrationListCommand(integrationCmd);
16717
+ registerIntegrationLoginCommand(integrationCmd);
16565
16718
  var reminderCmd = program.command("reminder").description("Reminder operations");
16566
16719
  registerReminderScheduleCommand(reminderCmd);
16567
16720
  registerReminderListCommand(reminderCmd);
package/dist/core.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resolveSlockCliPath,
10
10
  resolveWorkspaceDirectoryPath,
11
11
  scanWorkspaceDirectories
12
- } from "./chunk-HSBOURQE.js";
12
+ } from "./chunk-LPRTPDGH.js";
13
13
  import {
14
14
  subscribeDaemonLogs
15
15
  } from "./chunk-KNMCE6WB.js";
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  DAEMON_CLI_USAGE,
4
4
  DaemonCore,
5
5
  parseDaemonCliArgs
6
- } from "./chunk-HSBOURQE.js";
6
+ } from "./chunk-LPRTPDGH.js";
7
7
  import "./chunk-KNMCE6WB.js";
8
8
 
9
9
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/daemon",
3
- "version": "0.52.2",
3
+ "version": "0.53.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"
@@ -36,6 +36,7 @@
36
36
  "release:alpha": "npm version prerelease --preid=alpha --no-git-tag-version && cd ../.. && pnpm install --lockfile-only && git add packages/daemon/package.json pnpm-lock.yaml && git commit -m \"chore: bump @slock-ai/daemon to v$(node -p \"require('./packages/daemon/package.json').version\")\" && git tag daemon-v$(node -p \"require('./packages/daemon/package.json').version\") && git push && git push --tags"
37
37
  },
38
38
  "dependencies": {
39
+ "@jackwener/opencli": "^1.8.0",
39
40
  "@modelcontextprotocol/sdk": "^1.29.0",
40
41
  "commander": "^12.1.0",
41
42
  "https-proxy-agent": "^7.0.6",