pikiloom 0.4.26 → 0.4.27

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 (155) hide show
  1. package/dashboard/dist/assets/{AgentTab-BNJ73P0m.js → AgentTab-DIP-y4dl.js} +1 -1
  2. package/dashboard/dist/assets/ConnectionModal-BCfvhkiI.js +1 -0
  3. package/dashboard/dist/assets/{DirBrowser-D-GT-G-w.js → DirBrowser-Dot7ua9-.js} +1 -1
  4. package/dashboard/dist/assets/{ExtensionsTab-BrJMulJR.js → ExtensionsTab-GioMx4S6.js} +1 -1
  5. package/dashboard/dist/assets/{IMAccessTab-auPnx468.js → IMAccessTab-C9_r8jJP.js} +1 -1
  6. package/dashboard/dist/assets/{Modal-lYfO9m-I.js → Modal-M1AP1sgR.js} +1 -1
  7. package/dashboard/dist/assets/{Modals-PRdWOIY9.js → Modals-CCm3UUfJ.js} +1 -1
  8. package/dashboard/dist/assets/{Select-BhbU-rll.js → Select-YubDM16P.js} +1 -1
  9. package/dashboard/dist/assets/{SessionPanel-_0dLWvEb.js → SessionPanel-Cnv9o96n.js} +1 -1
  10. package/dashboard/dist/assets/SystemTab-VgNivIe3.js +1 -0
  11. package/dashboard/dist/assets/index-BY7l20bi.js +23 -0
  12. package/dashboard/dist/assets/index-BlqvzDUL.js +3 -0
  13. package/dashboard/dist/assets/index-CXIN3nTr.css +1 -0
  14. package/dashboard/dist/assets/{shared-C5uXvhZJ.js → shared-C0SAInnT.js} +1 -1
  15. package/dashboard/dist/index.html +2 -2
  16. package/dist/agent/acp-client.js +0 -59
  17. package/dist/agent/artifacts.js +1 -38
  18. package/dist/agent/auto-update.js +2 -78
  19. package/dist/agent/await-resume.js +0 -20
  20. package/dist/agent/cli/auth.js +3 -51
  21. package/dist/agent/cli/catalog.js +0 -3
  22. package/dist/agent/cli/detector.js +1 -19
  23. package/dist/agent/cli/index.js +0 -3
  24. package/dist/agent/cli/registry.js +0 -25
  25. package/dist/agent/driver.js +0 -14
  26. package/dist/agent/drivers/claude-tui.js +2 -769
  27. package/dist/agent/drivers/claude.js +12 -631
  28. package/dist/agent/drivers/codex.js +11 -162
  29. package/dist/agent/drivers/gemini.js +6 -114
  30. package/dist/agent/drivers/hermes.js +0 -128
  31. package/dist/agent/goal.js +0 -45
  32. package/dist/agent/handover.js +1 -46
  33. package/dist/agent/images.js +7 -133
  34. package/dist/agent/index.js +1 -29
  35. package/dist/agent/mcp/bridge.js +1 -197
  36. package/dist/agent/mcp/extensions.js +5 -84
  37. package/dist/agent/mcp/oauth.js +2 -69
  38. package/dist/agent/mcp/registry.js +2 -33
  39. package/dist/agent/mcp/session-server.js +2 -45
  40. package/dist/agent/mcp/tools/ask-user.js +0 -6
  41. package/dist/agent/mcp/tools/await-resume.js +0 -15
  42. package/dist/agent/mcp/tools/goal.js +0 -25
  43. package/dist/agent/mcp/tools/types.js +0 -5
  44. package/dist/agent/mcp/tools/workspace.js +0 -23
  45. package/dist/agent/npm.js +0 -14
  46. package/dist/agent/session.js +86 -196
  47. package/dist/agent/skill-installer.js +3 -70
  48. package/dist/agent/skills.js +0 -43
  49. package/dist/agent/stream.js +5 -148
  50. package/dist/agent/types.js +0 -12
  51. package/dist/agent/utils.js +2 -120
  52. package/dist/bot/bot.js +3 -468
  53. package/dist/bot/command-ui.js +0 -45
  54. package/dist/bot/commands.js +0 -51
  55. package/dist/bot/headless-bot.js +0 -21
  56. package/dist/bot/host.js +1 -5
  57. package/dist/bot/human-loop.js +0 -8
  58. package/dist/bot/menu.js +0 -3
  59. package/dist/bot/orchestration.js +0 -3
  60. package/dist/bot/render-shared.js +0 -63
  61. package/dist/bot/session-hub.js +14 -92
  62. package/dist/bot/session-status.js +1 -5
  63. package/dist/bot/streaming.js +0 -53
  64. package/dist/browser-profile.js +0 -51
  65. package/dist/browser-supervisor.js +0 -75
  66. package/dist/catalog/cli-tools.js +0 -53
  67. package/dist/catalog/index.js +0 -18
  68. package/dist/catalog/local-models.js +0 -18
  69. package/dist/catalog/mcp-servers.js +0 -35
  70. package/dist/catalog/skill-repos.js +0 -32
  71. package/dist/channels/base.js +0 -19
  72. package/dist/channels/dingtalk/bot.js +0 -3
  73. package/dist/channels/dingtalk/channel.js +0 -33
  74. package/dist/channels/discord/bot.js +0 -7
  75. package/dist/channels/discord/channel.js +0 -22
  76. package/dist/channels/feishu/bot.js +0 -71
  77. package/dist/channels/feishu/channel.js +0 -78
  78. package/dist/channels/feishu/markdown.js +0 -5
  79. package/dist/channels/feishu/render.js +0 -45
  80. package/dist/channels/health.js +0 -46
  81. package/dist/channels/slack/bot.js +0 -7
  82. package/dist/channels/slack/channel.js +1 -27
  83. package/dist/channels/states.js +0 -3
  84. package/dist/channels/telegram/bot.js +0 -60
  85. package/dist/channels/telegram/channel.js +2 -99
  86. package/dist/channels/telegram/directory.js +0 -3
  87. package/dist/channels/telegram/live-preview.js +0 -8
  88. package/dist/channels/telegram/render.js +0 -30
  89. package/dist/channels/wecom/bot.js +0 -3
  90. package/dist/channels/wecom/channel.js +0 -58
  91. package/dist/channels/weixin/api.js +0 -9
  92. package/dist/channels/weixin/bot.js +0 -88
  93. package/dist/channels/weixin/channel.js +0 -6
  94. package/dist/cli/autostart.js +1 -44
  95. package/dist/cli/channel-supervisor.js +1 -59
  96. package/dist/cli/channels.js +0 -6
  97. package/dist/cli/main.js +1 -145
  98. package/dist/cli/onboarding.js +0 -6
  99. package/dist/cli/run.js +0 -11
  100. package/dist/cli/setup-wizard.js +0 -3
  101. package/dist/core/config/runtime-config.js +0 -38
  102. package/dist/core/config/user-config.js +0 -63
  103. package/dist/core/config/validation.js +0 -9
  104. package/dist/core/constants.js +0 -268
  105. package/dist/core/git.js +0 -31
  106. package/dist/core/legacy-compat.js +1 -28
  107. package/dist/core/logging.js +0 -3
  108. package/dist/core/platform.js +0 -28
  109. package/dist/core/process-control.js +0 -8
  110. package/dist/core/secrets/index.js +0 -17
  111. package/dist/core/secrets/inline-seal.js +0 -7
  112. package/dist/core/secrets/ref.js +0 -19
  113. package/dist/core/secrets/resolver.js +0 -8
  114. package/dist/core/secrets/store.js +1 -12
  115. package/dist/core/utils.js +1 -37
  116. package/dist/core/version.js +0 -3
  117. package/dist/dashboard/platform.js +1 -35
  118. package/dist/dashboard/routes/agents.js +2 -71
  119. package/dist/dashboard/routes/cli.js +0 -27
  120. package/dist/dashboard/routes/config.js +0 -54
  121. package/dist/dashboard/routes/extensions.js +0 -132
  122. package/dist/dashboard/routes/local-models.js +0 -68
  123. package/dist/dashboard/routes/models.js +0 -46
  124. package/dist/dashboard/routes/sessions.js +0 -100
  125. package/dist/dashboard/runtime.js +1 -58
  126. package/dist/dashboard/server.js +0 -49
  127. package/dist/dashboard/session-control.js +2 -69
  128. package/dist/model/catalog.js +1 -17
  129. package/dist/model/index.js +0 -15
  130. package/dist/model/injector.js +1 -181
  131. package/dist/model/provider-models.js +1 -48
  132. package/dist/model/responses-bridge.js +3 -45
  133. package/dist/model/store.js +0 -17
  134. package/dist/model/types.js +0 -12
  135. package/dist/model/validation.js +1 -18
  136. package/dist/pikichannel/adapter-pikiloom.js +0 -31
  137. package/dist/pikichannel/code.js +0 -12
  138. package/dist/pikichannel/codec.js +0 -14
  139. package/dist/pikichannel/host.js +4 -33
  140. package/dist/pikichannel/protocol.js +0 -52
  141. package/dist/pikichannel/rendezvous-broker.js +5 -26
  142. package/dist/pikichannel/rendezvous-host.js +3 -16
  143. package/dist/pikichannel/server.js +6 -55
  144. package/dist/pikichannel/transport.js +0 -25
  145. package/dist/pikichannel/transports/webrtc-host.js +0 -18
  146. package/dist/pikichannel/transports/webrtc-shared.js +4 -34
  147. package/dist/pikichannel/transports/websocket-host.js +1 -14
  148. package/dist/pikichannel/turn.js +2 -69
  149. package/dist/pikichannel/web/sdk.js +14 -71
  150. package/package.json +1 -1
  151. package/dashboard/dist/assets/ConnectionModal-Dt05H2f9.js +0 -1
  152. package/dashboard/dist/assets/SystemTab-CN_QrRKt.js +0 -1
  153. package/dashboard/dist/assets/index-CP3LYJR2.js +0 -23
  154. package/dashboard/dist/assets/index-C_neLnaO.js +0 -3
  155. package/dashboard/dist/assets/index-CyCspgtc.css +0 -1
package/dist/core/git.js CHANGED
@@ -1,22 +1,5 @@
1
- /**
2
- * Git working-tree status as a cross-platform OS primitive.
3
- *
4
- * One bounded `git status --porcelain=v2 --branch` invocation, parsed into a
5
- * structured {@link GitStatus}. Porcelain v2 is machine-stable across git
6
- * versions and locales, so we count entry lines rather than scraping human
7
- * output. A single reader feeds both the IM `/status` command and the Dashboard
8
- * workspace view — no git logic is duplicated in the channels or the SPA.
9
- */
10
1
  import { spawnSync } from 'node:child_process';
11
2
  import { GIT_STATUS_TIMEOUT_MS } from './constants.js';
12
- /**
13
- * Read the git status of `dir`. Returns `null` for a non-repo, a missing git
14
- * binary, or a timeout — callers simply omit the git section. Never throws.
15
- *
16
- * Walks up from `dir` like git itself, so a workspace nested below the repo root
17
- * is still recognised. Uses `GIT_OPTIONAL_LOCKS=0` to avoid contending with
18
- * other git processes for the index lock.
19
- */
20
3
  export function readGitStatus(dir) {
21
4
  if (!dir)
22
5
  return null;
@@ -35,7 +18,6 @@ export function readGitStatus(dir) {
35
18
  return null;
36
19
  }
37
20
  }
38
- /** Parse `git status --porcelain=v2 --branch` output. Exported for testing. */
39
21
  export function parseGitStatusV2(stdout) {
40
22
  let branch = null;
41
23
  let detached = false;
@@ -75,7 +57,6 @@ export function parseGitStatusV2(stdout) {
75
57
  }
76
58
  }
77
59
  else if (line.startsWith('1 ') || line.startsWith('2 ')) {
78
- // Ordinary / renamed entry: field 2 is the XY status (X=index, Y=worktree).
79
60
  const xy = line.split(' ')[1] || '..';
80
61
  if (xy[0] && xy[0] !== '.')
81
62
  staged++;
@@ -83,7 +64,6 @@ export function parseGitStatusV2(stdout) {
83
64
  unstaged++;
84
65
  }
85
66
  else if (line.startsWith('u ')) {
86
- // Unmerged (conflict) — count as a working-tree change.
87
67
  unstaged++;
88
68
  }
89
69
  else if (line.startsWith('? ')) {
@@ -103,17 +83,6 @@ export function parseGitStatusV2(stdout) {
103
83
  changed: staged + unstaged + untracked,
104
84
  };
105
85
  }
106
- /**
107
- * Render a {@link GitStatus} into a single friendly line (no channel-specific
108
- * markup), e.g.:
109
- *
110
- * main ↑2 ↓1 · 5 changed (3 staged · 2 untracked)
111
- * feature/x · no upstream · clean
112
- * (detached a1b2c3d) · 1 changed
113
- *
114
- * Returns `null` when there is no git status to show, so callers can omit the
115
- * line entirely.
116
- */
117
86
  export function formatGitStatusLine(git) {
118
87
  if (!git)
119
88
  return null;
@@ -1,24 +1,7 @@
1
- /**
2
- * One-time backward-compat shims for the project rename.
3
- *
4
- * The orchestrator shipped as `pikiclaw` and is now `pikiloom`. Both run once at
5
- * process startup — BEFORE any config is read or any lock / PID file is taken —
6
- * so existing `pikiclaw` installs keep their settings, credentials, managed
7
- * browser profile and skills with zero user action.
8
- *
9
- * Remove this file (and the LEGACY_* constants) a couple of releases after the
10
- * rename has propagated.
11
- */
12
1
  import fs from 'node:fs';
13
2
  import os from 'node:os';
14
3
  import path from 'node:path';
15
4
  import { STATE_DIR_NAME, LEGACY_STATE_DIR_NAMES, ENV_PREFIX, LEGACY_ENV_PREFIXES, } from './constants.js';
16
- /**
17
- * Mirror every legacy-prefixed env var (`PIKICLAW_*`) onto the matching
18
- * `PIKILOOM_*` name when the new name is unset. Covers user-set vars (shell
19
- * profiles, docker-compose, systemd units) AND internal ones a still-old parent
20
- * process may have set across an upgrade boundary.
21
- */
22
5
  export function hydrateLegacyEnv() {
23
6
  for (const legacy of LEGACY_ENV_PREFIXES) {
24
7
  for (const [key, value] of Object.entries(process.env)) {
@@ -32,14 +15,6 @@ export function hydrateLegacyEnv() {
32
15
  }
33
16
  }
34
17
  }
35
- /**
36
- * Migrate the legacy state dir (`~/.pikiclaw`) → `~/.pikiloom`, exactly once.
37
- *
38
- * No-op when the new dir already exists (migrated or fresh install) or no legacy
39
- * dir is present (brand-new user). A same-volume rename is atomic; on a
40
- * cross-device failure we fall back to a recursive copy and deliberately leave
41
- * the old dir in place so a partial/failed copy can never lose user data.
42
- */
43
18
  export function migrateLegacyStateDir() {
44
19
  try {
45
20
  const home = os.homedir();
@@ -54,13 +29,11 @@ export function migrateLegacyStateDir() {
54
29
  fs.renameSync(prev, next);
55
30
  }
56
31
  catch {
57
- // Cross-device or in-use: copy and keep the original as a safety net.
58
32
  fs.cpSync(prev, next, { recursive: true });
59
33
  }
60
- return; // migrated from the newest available legacy dir
34
+ return;
61
35
  }
62
36
  }
63
37
  catch {
64
- // Best-effort only — never block startup on migration.
65
38
  }
66
39
  }
@@ -1,6 +1,3 @@
1
- /**
2
- * Structured logging with scoped writers and file retention.
3
- */
4
1
  import fs from 'node:fs';
5
2
  import path from 'node:path';
6
3
  const LOG_LEVEL_WEIGHT = {
@@ -1,23 +1,12 @@
1
- /**
2
- * Cross-platform primitives. All OS-dependent behavior must route through here
3
- * so the rest of the codebase stays platform-neutral.
4
- */
5
1
  import os from 'node:os';
6
2
  import path from 'node:path';
7
3
  import which from 'which';
8
4
  export const IS_WIN = process.platform === 'win32';
9
5
  export const IS_MAC = process.platform === 'darwin';
10
6
  export const IS_LINUX = process.platform === 'linux';
11
- /**
12
- * User home directory. Re-reads each call so runtime `$HOME`/`$USERPROFILE`
13
- * overrides (and tests that mutate them) stay honored. Works on Windows
14
- * where `$HOME` is not set by default — `os.homedir()` falls back to
15
- * `$USERPROFILE`.
16
- */
17
7
  export function getHome() {
18
8
  return os.homedir();
19
9
  }
20
- /** Expand a leading `~` (or `~/`, `~\`) to the user's home directory. */
21
10
  export function expandTilde(p) {
22
11
  if (!p || p[0] !== '~')
23
12
  return p;
@@ -29,31 +18,14 @@ export function expandTilde(p) {
29
18
  }
30
19
  return p;
31
20
  }
32
- /** Locate an executable on PATH, honoring PATHEXT on Windows. */
33
21
  export function whichSync(cmd) {
34
22
  return which.sync(cmd, { nothrow: true }) || null;
35
23
  }
36
- /**
37
- * Encode an absolute workdir path as a single directory-name segment.
38
- * Mirrors Claude Code's scheme under `~/.claude/projects/`: every non
39
- * alphanumeric character collapses to `-`. Critically that includes
40
- * underscores and dots (e.g. `/path/to/harness_ppt` → `-path-to-harness-ppt`),
41
- * which matches the encoding Claude Code uses on disk. Replacing only path
42
- * separators leaves a workdir whose name contains `_` (or `.`) pointing at
43
- * a directory that does not exist, so session JSONL lookups silently fall
44
- * back to an empty/truncated result.
45
- */
46
24
  export function encodePathAsDirName(p) {
47
25
  return p.replace(/[^a-zA-Z0-9]/g, '-');
48
26
  }
49
- /**
50
- * Match a path segment regardless of separator. Useful for probing whether a
51
- * resolved script path runs under a given binary (e.g. `tsx`, `ts-node`)
52
- * without hardcoding `/` — which fails on Windows.
53
- */
54
27
  export function pathContainsSegment(p, segment) {
55
28
  const escaped = segment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
56
29
  return new RegExp(`[\\\\/]${escaped}([\\\\/]|$)`).test(p);
57
30
  }
58
- /** Null-redirect suffix for shell commands. */
59
31
  export const DEV_NULL_REDIRECT = IS_WIN ? '2>nul' : '2>/dev/null';
@@ -1,6 +1,3 @@
1
- /**
2
- * Process lifecycle: restart coordination, watchdog, and process tree termination.
3
- */
4
1
  import fs from 'node:fs';
5
2
  import os from 'node:os';
6
3
  import path from 'node:path';
@@ -10,7 +7,6 @@ import { STATE_DIR_NAME } from './constants.js';
10
7
  export const PROCESS_RESTART_EXIT_CODE = 75;
11
8
  export const PROCESS_RESTART_STATE_FILE_ENV = 'PIKILOOM_RESTART_STATE_FILE';
12
9
  const DAEMON_PID_FILENAME = 'pikiloom.pid';
13
- /** Path to the daemon PID file used by `pikiloom stop`. */
14
10
  export function getDaemonPidFilePath() {
15
11
  return path.join(os.homedir(), STATE_DIR_NAME, DAEMON_PID_FILENAME);
16
12
  }
@@ -38,7 +34,6 @@ export function readDaemonPidFile() {
38
34
  return null;
39
35
  }
40
36
  }
41
- /** True if a process with `pid` is currently running (POSIX kill -0 / win32 tasklist). */
42
37
  export function isProcessAlive(pid) {
43
38
  if (!pid || pid <= 0)
44
39
  return false;
@@ -48,7 +43,6 @@ export function isProcessAlive(pid) {
48
43
  }
49
44
  catch (err) {
50
45
  const code = err?.code;
51
- // EPERM means the process exists but we cannot signal it — still alive.
52
46
  return code === 'EPERM';
53
47
  }
54
48
  }
@@ -100,7 +94,6 @@ export function getDefaultRestartCmd() {
100
94
  const parts = isTsxLoader ? ['tsx', argv1] : process.argv.slice(0, 2);
101
95
  return parts.map(arg => arg.includes(' ') ? `"${arg}"` : arg).join(' ');
102
96
  }
103
- // Running from an installed package (e.g. npm install -g) — reuse the same entry point
104
97
  if (argv1.endsWith('.js') && (argv1.includes('pikiloom') || argv1.includes('pikiloom'))) {
105
98
  const nodeBin = argv0.includes(' ') ? `"${argv0}"` : argv0;
106
99
  const entry = argv1.includes(' ') ? `"${argv1}"` : argv1;
@@ -216,7 +209,6 @@ function buildRestartEnvForSpawn(extraEnv) {
216
209
  return env;
217
210
  }
218
211
  function spawnReplacementProcess(bin, args, env, log) {
219
- // npx/npx.cmd needs shell resolution; node.exe does not
220
212
  const needsShell = process.platform === 'win32' && !bin.endsWith('node.exe');
221
213
  const child = spawn(needsShell ? `"${bin}"` : bin, args, {
222
214
  stdio: 'inherit',
@@ -1,23 +1,9 @@
1
- /**
2
- * Barrel for the pikiloom credential vault.
3
- *
4
- * Pikiloom owns its own credential layer rather than delegating to per-agent
5
- * config files: when an agent is spawned, the active Profile's credentials
6
- * are resolved on the fly and injected as env vars / generated config files.
7
- * This means setting.json never holds a raw secret in the default flow, and
8
- * adding a new agent (Hermes, OpenCode, …) does not duplicate credential UX.
9
- */
10
1
  export { KEYCHAIN_SERVICE, isCredentialRef, describeCredentialRef } from './ref.js';
11
2
  export { resolveCredential, tryResolveCredential } from './resolver.js';
12
3
  export { isKeychainAvailable, readKeychain, writeKeychain, deleteKeychain, } from './store.js';
13
4
  export { sealInline, unsealInline } from './inline-seal.js';
14
5
  import { writeKeychain, deleteKeychain, isKeychainAvailable } from './store.js';
15
6
  import { sealInline } from './inline-seal.js';
16
- /**
17
- * Convenience: store a freshly-pasted secret using the safest available
18
- * backend. Returns the CredentialRef the caller should persist alongside
19
- * the Provider record.
20
- */
21
7
  export async function persistSecret(account, plaintext) {
22
8
  if (await isKeychainAvailable()) {
23
9
  try {
@@ -25,12 +11,10 @@ export async function persistSecret(account, plaintext) {
25
11
  return { source: 'keychain', account };
26
12
  }
27
13
  catch {
28
- // fall through to inline seal
29
14
  }
30
15
  }
31
16
  return { source: 'inline', sealed: sealInline(plaintext) };
32
17
  }
33
- /** Best-effort cleanup when a Provider is deleted. */
34
18
  export async function forgetSecret(ref) {
35
19
  if (ref.source === 'keychain') {
36
20
  try {
@@ -38,5 +22,4 @@ export async function forgetSecret(ref) {
38
22
  }
39
23
  catch { }
40
24
  }
41
- // env / command / inline references have nothing to clean
42
25
  }
@@ -1,9 +1,3 @@
1
- /**
2
- * Machine-bound AES-256-GCM seal for credential values when no OS keychain
3
- * is available. The derived key mixes hostname + a per-install random salt
4
- * stored alongside ~/.pikiloom/setting.json, so a sealed blob copied to a
5
- * different machine will not decrypt.
6
- */
7
1
  import crypto from 'node:crypto';
8
2
  import fs from 'node:fs';
9
3
  import os from 'node:os';
@@ -35,7 +29,6 @@ function deriveKey() {
35
29
  ]);
36
30
  return crypto.createHash('sha256').update(material).digest();
37
31
  }
38
- /** Returns base64 string `v1:<iv>:<ciphertext>:<tag>`. */
39
32
  export function sealInline(plaintext) {
40
33
  const key = deriveKey();
41
34
  const iv = crypto.randomBytes(12);
@@ -1,22 +1,4 @@
1
- /**
2
- * Credential reference — never store raw secrets in setting.json.
3
- *
4
- * Each credential is described by a small reference that can be dereferenced
5
- * at use time:
6
- * - keychain: stored in OS keychain, looked up by service+account
7
- * - env: read from process.env at use time
8
- * - command: exec a command and use its stdout (e.g. `op read`, `gh auth token`)
9
- * - inline: AES-GCM sealed blob bound to this machine (fallback)
10
- */
11
- /** Stable service name used when writing to the OS keychain. */
12
1
  export const KEYCHAIN_SERVICE = 'pikiloom';
13
- /**
14
- * Legacy keychain service names this build still reads from. The project was
15
- * renamed (pikiclaw → pikiloom); credentials written under the old service are
16
- * orphaned because lookups now use `KEYCHAIN_SERVICE`. `readKeychain` falls
17
- * back to these on a miss and lazily re-writes the secret under the current
18
- * service, so each orphaned item self-heals on first use.
19
- */
20
2
  export const LEGACY_KEYCHAIN_SERVICES = ['pikiclaw'];
21
3
  export function isCredentialRef(value) {
22
4
  if (!value || typeof value !== 'object')
@@ -30,7 +12,6 @@ export function isCredentialRef(value) {
30
12
  default: return false;
31
13
  }
32
14
  }
33
- /** Short, non-sensitive description of a credential reference for UI display. */
34
15
  export function describeCredentialRef(ref) {
35
16
  switch (ref.source) {
36
17
  case 'keychain': return `keychain:${ref.account}`;
@@ -1,10 +1,3 @@
1
- /**
2
- * Resolve a CredentialRef to a plaintext secret.
3
- *
4
- * Resolved values are returned to the caller and immediately consumed (e.g.
5
- * injected into a child process env at spawn time). Pikiloom never persists
6
- * the resolved plaintext in long-lived state.
7
- */
8
1
  import { execFile } from 'node:child_process';
9
2
  import { promisify } from 'node:util';
10
3
  import { readKeychain } from './store.js';
@@ -54,7 +47,6 @@ export async function resolveCredential(ref, opts = {}) {
54
47
  }
55
48
  }
56
49
  }
57
- /** Resolve to null on error rather than throwing — useful for status checks. */
58
50
  export async function tryResolveCredential(ref, opts = {}) {
59
51
  try {
60
52
  return await resolveCredential(ref, opts);
@@ -1,15 +1,9 @@
1
- /**
2
- * OS keychain access — lazy import of @napi-rs/keyring (optional dep).
3
- * If keychain is unavailable, callers fall back to inline-seal.
4
- */
5
1
  import { KEYCHAIN_SERVICE, LEGACY_KEYCHAIN_SERVICES } from './ref.js';
6
- let keyringModule; // undefined = not tried, null = failed
2
+ let keyringModule;
7
3
  async function loadKeyring() {
8
4
  if (keyringModule !== undefined)
9
5
  return keyringModule;
10
6
  try {
11
- // Optional dep — tsc can't see it without the package installed; use a
12
- // dynamic, untyped import so the build succeeds with or without it.
13
7
  const importer = new Function('m', 'return import(m)');
14
8
  const mod = await importer('@napi-rs/keyring');
15
9
  keyringModule = mod;
@@ -19,7 +13,6 @@ async function loadKeyring() {
19
13
  }
20
14
  return keyringModule;
21
15
  }
22
- /** Reset the cached module — used by tests. */
23
16
  export function _resetKeychainCache() {
24
17
  keyringModule = undefined;
25
18
  }
@@ -37,7 +30,6 @@ export async function readKeychain(account) {
37
30
  return typeof value === 'string' && value.length > 0 ? value : null;
38
31
  }
39
32
  catch (e) {
40
- // keyring-rs returns NoEntry as an error — treat as missing
41
33
  if (/NoEntry|no.such|not.found/i.test(e?.message || ''))
42
34
  return null;
43
35
  throw e;
@@ -46,8 +38,6 @@ export async function readKeychain(account) {
46
38
  const current = readUnder(KEYCHAIN_SERVICE);
47
39
  if (current != null)
48
40
  return current;
49
- // Pre-rename fallback: recover orphaned items and migrate them forward so the
50
- // legacy lookup only ever happens once per account.
51
41
  for (const legacy of LEGACY_KEYCHAIN_SERVICES) {
52
42
  const value = readUnder(legacy);
53
43
  if (value == null)
@@ -56,7 +46,6 @@ export async function readKeychain(account) {
56
46
  new mod.Entry(KEYCHAIN_SERVICE, account).setPassword(value);
57
47
  }
58
48
  catch {
59
- // best-effort migration — return the recovered secret regardless
60
49
  }
61
50
  return value;
62
51
  }
@@ -1,13 +1,6 @@
1
- /**
2
- * Pure utility functions shared across all layers. No filesystem side effects, no state dependencies.
3
- */
4
1
  import fs from 'node:fs';
5
2
  import path from 'node:path';
6
3
  import { whichSync as platformWhichSync } from './platform.js';
7
- /**
8
- * If `dir` has a .gitignore, ignore managed `.pikiloom` state without hiding
9
- * `.pikiloom/skills`, which may be committed as project skills.
10
- */
11
4
  export function ensureGitignore(dir) {
12
5
  try {
13
6
  const gi = path.join(dir, '.gitignore');
@@ -34,7 +27,7 @@ export function ensureGitignore(dir) {
34
27
  return;
35
28
  fs.writeFileSync(gi, next);
36
29
  }
37
- catch { /* best-effort */ }
30
+ catch { }
38
31
  }
39
32
  export function envBool(name, def) {
40
33
  const raw = process.env[name];
@@ -82,38 +75,12 @@ export function shellSplit(str) {
82
75
  return args;
83
76
  }
84
77
  export const whichSync = platformWhichSync;
85
- /**
86
- * Strip ANSI terminal control sequences from a string. Covers the families
87
- * pikiloom runs into when scraping PTY screens (cursor positioning, SGR
88
- * colour / bold / italic, OSC titles, plus orphaned ESC bytes):
89
- *
90
- * CSI: ESC [ ... — colours, cursor moves, line clears
91
- * OSC: ESC ] ... (BEL | ESC \) — set window title, hyperlinks
92
- * Other: ESC <single char> — single-char escapes (RIS, IND, …)
93
- *
94
- * Some IM channels strip the raw ESC byte but pass through the trailing
95
- * `[3G` / `[1m` / `[38;2;…m` payload, which is how the user ends up seeing
96
- * "[3G你把" in Feishu. The regex matches with-or-without the leading ESC so
97
- * already-mangled output still gets cleaned. The leading-bracket fallback is
98
- * conservative — it only fires for known control verbs (digits/`;` then a
99
- * SGR/cursor letter), so legitimate text like "[3 second timeout]" survives.
100
- */
101
78
  export function stripAnsiEscapes(input) {
102
79
  if (!input)
103
80
  return input;
104
- // Drop OSC (operating system command) sequences first so their payload
105
- // doesn't confuse the CSI matcher.
106
81
  let out = input.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, '');
107
- // CSI with the leading ESC byte — match any final byte, even param-less
108
- // (e.g. `\x1b[A` cursor-up, `\x1b[m` reset SGR). The ESC byte unambiguously
109
- // signals a control sequence so we can be liberal here.
110
82
  out = out.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '');
111
- // CSI without the ESC byte (some IM channels strip the raw \x1b but pass
112
- // through the `[3G` / `[38;2;…m` payload). Require at least one digit /
113
- // semicolon in the params so legitimate text like "see [issue #42]" or
114
- // "[3 second timeout]" doesn't get nibbled.
115
83
  out = out.replace(/\[[0-9;?]+[A-Za-z]/g, '');
116
- // Any remaining ESC + single byte (RIS, IND, NEL, …).
117
84
  out = out.replace(/\x1b[@-Z\\-_]/g, '');
118
85
  return out;
119
86
  }
@@ -153,8 +120,6 @@ export function parseAllowedChatIds(raw) {
153
120
  if (!v)
154
121
  continue;
155
122
  const n = parseInt(v, 10);
156
- // If the string is purely numeric, store as number for backward compat (Telegram).
157
- // Otherwise store as string (Feishu, Discord, etc.).
158
123
  if (!Number.isNaN(n) && String(n) === v)
159
124
  ids.add(n);
160
125
  else if (v)
@@ -204,7 +169,6 @@ export function buildPrompt(text, files) {
204
169
  return text;
205
170
  return `${text || 'Please analyze this.'}\n\n[Files: ${files.map(f => path.basename(f)).join(', ')}]`;
206
171
  }
207
- /** Race a promise against a timeout, resolving with `fallback` on timeout or rejection. */
208
172
  export function withTimeoutFallback(promise, timeoutMs, fallback) {
209
173
  return new Promise(resolve => {
210
174
  let settled = false;
@@ -1,6 +1,3 @@
1
- /**
2
- * Reads the package version from package.json at startup.
3
- */
4
1
  import fs from 'node:fs';
5
2
  import path from 'node:path';
6
3
  import { fileURLToPath } from 'node:url';
@@ -1,23 +1,12 @@
1
- /**
2
- * Platform detection helpers.
3
- *
4
- * macOS permission checks, terminal detection, JXA scripts, and other OS-level utilities.
5
- */
6
1
  import fs from 'node:fs';
7
2
  import path from 'node:path';
8
3
  import os from 'node:os';
9
4
  import { execFileSync, execSync } from 'node:child_process';
10
5
  import { DASHBOARD_PERMISSION_TIMEOUTS, DASHBOARD_PERMISSION_CACHE_TTL_MS, } from '../core/constants.js';
11
- // ---------------------------------------------------------------------------
12
- // Permission pane URLs (macOS)
13
- // ---------------------------------------------------------------------------
14
6
  const permissionPaneUrls = {
15
7
  screenRecording: 'x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture',
16
8
  fullDiskAccess: 'x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles',
17
9
  };
18
- // ---------------------------------------------------------------------------
19
- // JXA helpers
20
- // ---------------------------------------------------------------------------
21
10
  function runJxa(script, timeout = DASHBOARD_PERMISSION_TIMEOUTS.jxaDefault) {
22
11
  try {
23
12
  return String(execFileSync('osascript', ['-l', 'JavaScript', '-e', script], { encoding: 'utf8', timeout })).trim().toLowerCase();
@@ -59,9 +48,6 @@ function openPermissionSettings(permission) {
59
48
  return false;
60
49
  }
61
50
  }
62
- // ---------------------------------------------------------------------------
63
- // Permission checks
64
- // ---------------------------------------------------------------------------
65
51
  export function checkPermissions() {
66
52
  const r = {};
67
53
  if (process.platform !== 'darwin') {
@@ -84,15 +70,6 @@ export function checkPermissions() {
84
70
  }
85
71
  return r;
86
72
  }
87
- // ---------------------------------------------------------------------------
88
- // Cached probes for the polling dashboard
89
- // ---------------------------------------------------------------------------
90
- // `/api/state` is polled (~1.5s while a channel validates) and both probes below
91
- // spawn subprocesses — checkPermissions() runs screencapture + an `ls` shell,
92
- // detectHostTerminalApp() runs a `ps` process-tree walk — so they must never run
93
- // per request. The host terminal is fixed for the process lifetime; permission
94
- // grants change rarely, so a short TTL is plenty and requestPermission()
95
- // invalidates the cache so a user-driven grant surfaces on the next poll.
96
73
  let permissionsCache = null;
97
74
  let hostTerminalAppCache = null;
98
75
  export function getPermissionsStatus() {
@@ -109,7 +86,7 @@ export function getHostTerminalApp() {
109
86
  return hostTerminalAppCache.value;
110
87
  }
111
88
  export function requestPermission(permission) {
112
- permissionsCache = null; // a request can change grant state — force the next poll to re-probe
89
+ permissionsCache = null;
113
90
  if (process.platform !== 'darwin') {
114
91
  return {
115
92
  ok: false,
@@ -154,17 +131,10 @@ export function requestPermission(permission) {
154
131
  export function isValidPermissionKey(value) {
155
132
  return value in permissionPaneUrls;
156
133
  }
157
- // ---------------------------------------------------------------------------
158
- // Terminal detection
159
- // ---------------------------------------------------------------------------
160
- /** Walk the process tree upward to find the host terminal / IDE that launched pikiloom. Works on macOS and Linux. */
161
134
  export function detectHostTerminalApp() {
162
135
  if (process.platform !== 'darwin' && process.platform !== 'linux')
163
136
  return null;
164
137
  try {
165
- // Patterns to match in the comm/exe name (case-insensitive on Linux where names vary)
166
- // macOS: Terminal, iTerm2, Warp; Linux: gnome-terminal, konsole, xfce4-terminal, xterm, tilix, foot, sakura, terminology
167
- // Cross-platform: Alacritty, kitty, WezTerm, Hyper, VS Code, Cursor, Windsurf
168
138
  const patterns = [
169
139
  'Terminal', 'iTerm', 'Warp',
170
140
  'Alacritty', 'alacritty', 'kitty', 'WezTerm', 'wezterm', 'Hyper',
@@ -176,18 +146,14 @@ export function detectHostTerminalApp() {
176
146
  if (!output)
177
147
  return null;
178
148
  const base = path.basename(output);
179
- // Map comm name → human-readable display name
180
149
  const nameMap = [
181
- // macOS
182
150
  ['iTerm', 'iTerm2'],
183
151
  ['Code Helper', 'VS Code'],
184
152
  ['Cursor Helper', 'Cursor'],
185
153
  ['Windsurf Helper', 'Windsurf'],
186
- // Cross-platform IDE wrappers (Linux uses "code" binary directly)
187
154
  ['code', 'VS Code'],
188
155
  ['cursor', 'Cursor'],
189
156
  ['windsurf', 'Windsurf'],
190
- // Terminal emulators
191
157
  ['gnome-terminal', 'GNOME Terminal'],
192
158
  ['xfce4-terminal', 'Xfce Terminal'],
193
159
  ['Terminal', 'Terminal'],