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.
- package/dashboard/dist/assets/{AgentTab-BNJ73P0m.js → AgentTab-DIP-y4dl.js} +1 -1
- package/dashboard/dist/assets/ConnectionModal-BCfvhkiI.js +1 -0
- package/dashboard/dist/assets/{DirBrowser-D-GT-G-w.js → DirBrowser-Dot7ua9-.js} +1 -1
- package/dashboard/dist/assets/{ExtensionsTab-BrJMulJR.js → ExtensionsTab-GioMx4S6.js} +1 -1
- package/dashboard/dist/assets/{IMAccessTab-auPnx468.js → IMAccessTab-C9_r8jJP.js} +1 -1
- package/dashboard/dist/assets/{Modal-lYfO9m-I.js → Modal-M1AP1sgR.js} +1 -1
- package/dashboard/dist/assets/{Modals-PRdWOIY9.js → Modals-CCm3UUfJ.js} +1 -1
- package/dashboard/dist/assets/{Select-BhbU-rll.js → Select-YubDM16P.js} +1 -1
- package/dashboard/dist/assets/{SessionPanel-_0dLWvEb.js → SessionPanel-Cnv9o96n.js} +1 -1
- package/dashboard/dist/assets/SystemTab-VgNivIe3.js +1 -0
- package/dashboard/dist/assets/index-BY7l20bi.js +23 -0
- package/dashboard/dist/assets/index-BlqvzDUL.js +3 -0
- package/dashboard/dist/assets/index-CXIN3nTr.css +1 -0
- package/dashboard/dist/assets/{shared-C5uXvhZJ.js → shared-C0SAInnT.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/agent/acp-client.js +0 -59
- package/dist/agent/artifacts.js +1 -38
- package/dist/agent/auto-update.js +2 -78
- package/dist/agent/await-resume.js +0 -20
- package/dist/agent/cli/auth.js +3 -51
- package/dist/agent/cli/catalog.js +0 -3
- package/dist/agent/cli/detector.js +1 -19
- package/dist/agent/cli/index.js +0 -3
- package/dist/agent/cli/registry.js +0 -25
- package/dist/agent/driver.js +0 -14
- package/dist/agent/drivers/claude-tui.js +2 -769
- package/dist/agent/drivers/claude.js +12 -631
- package/dist/agent/drivers/codex.js +11 -162
- package/dist/agent/drivers/gemini.js +6 -114
- package/dist/agent/drivers/hermes.js +0 -128
- package/dist/agent/goal.js +0 -45
- package/dist/agent/handover.js +1 -46
- package/dist/agent/images.js +7 -133
- package/dist/agent/index.js +1 -29
- package/dist/agent/mcp/bridge.js +1 -197
- package/dist/agent/mcp/extensions.js +5 -84
- package/dist/agent/mcp/oauth.js +2 -69
- package/dist/agent/mcp/registry.js +2 -33
- package/dist/agent/mcp/session-server.js +2 -45
- package/dist/agent/mcp/tools/ask-user.js +0 -6
- package/dist/agent/mcp/tools/await-resume.js +0 -15
- package/dist/agent/mcp/tools/goal.js +0 -25
- package/dist/agent/mcp/tools/types.js +0 -5
- package/dist/agent/mcp/tools/workspace.js +0 -23
- package/dist/agent/npm.js +0 -14
- package/dist/agent/session.js +86 -196
- package/dist/agent/skill-installer.js +3 -70
- package/dist/agent/skills.js +0 -43
- package/dist/agent/stream.js +5 -148
- package/dist/agent/types.js +0 -12
- package/dist/agent/utils.js +2 -120
- package/dist/bot/bot.js +3 -468
- package/dist/bot/command-ui.js +0 -45
- package/dist/bot/commands.js +0 -51
- package/dist/bot/headless-bot.js +0 -21
- package/dist/bot/host.js +1 -5
- package/dist/bot/human-loop.js +0 -8
- package/dist/bot/menu.js +0 -3
- package/dist/bot/orchestration.js +0 -3
- package/dist/bot/render-shared.js +0 -63
- package/dist/bot/session-hub.js +14 -92
- package/dist/bot/session-status.js +1 -5
- package/dist/bot/streaming.js +0 -53
- package/dist/browser-profile.js +0 -51
- package/dist/browser-supervisor.js +0 -75
- package/dist/catalog/cli-tools.js +0 -53
- package/dist/catalog/index.js +0 -18
- package/dist/catalog/local-models.js +0 -18
- package/dist/catalog/mcp-servers.js +0 -35
- package/dist/catalog/skill-repos.js +0 -32
- package/dist/channels/base.js +0 -19
- package/dist/channels/dingtalk/bot.js +0 -3
- package/dist/channels/dingtalk/channel.js +0 -33
- package/dist/channels/discord/bot.js +0 -7
- package/dist/channels/discord/channel.js +0 -22
- package/dist/channels/feishu/bot.js +0 -71
- package/dist/channels/feishu/channel.js +0 -78
- package/dist/channels/feishu/markdown.js +0 -5
- package/dist/channels/feishu/render.js +0 -45
- package/dist/channels/health.js +0 -46
- package/dist/channels/slack/bot.js +0 -7
- package/dist/channels/slack/channel.js +1 -27
- package/dist/channels/states.js +0 -3
- package/dist/channels/telegram/bot.js +0 -60
- package/dist/channels/telegram/channel.js +2 -99
- package/dist/channels/telegram/directory.js +0 -3
- package/dist/channels/telegram/live-preview.js +0 -8
- package/dist/channels/telegram/render.js +0 -30
- package/dist/channels/wecom/bot.js +0 -3
- package/dist/channels/wecom/channel.js +0 -58
- package/dist/channels/weixin/api.js +0 -9
- package/dist/channels/weixin/bot.js +0 -88
- package/dist/channels/weixin/channel.js +0 -6
- package/dist/cli/autostart.js +1 -44
- package/dist/cli/channel-supervisor.js +1 -59
- package/dist/cli/channels.js +0 -6
- package/dist/cli/main.js +1 -145
- package/dist/cli/onboarding.js +0 -6
- package/dist/cli/run.js +0 -11
- package/dist/cli/setup-wizard.js +0 -3
- package/dist/core/config/runtime-config.js +0 -38
- package/dist/core/config/user-config.js +0 -63
- package/dist/core/config/validation.js +0 -9
- package/dist/core/constants.js +0 -268
- package/dist/core/git.js +0 -31
- package/dist/core/legacy-compat.js +1 -28
- package/dist/core/logging.js +0 -3
- package/dist/core/platform.js +0 -28
- package/dist/core/process-control.js +0 -8
- package/dist/core/secrets/index.js +0 -17
- package/dist/core/secrets/inline-seal.js +0 -7
- package/dist/core/secrets/ref.js +0 -19
- package/dist/core/secrets/resolver.js +0 -8
- package/dist/core/secrets/store.js +1 -12
- package/dist/core/utils.js +1 -37
- package/dist/core/version.js +0 -3
- package/dist/dashboard/platform.js +1 -35
- package/dist/dashboard/routes/agents.js +2 -71
- package/dist/dashboard/routes/cli.js +0 -27
- package/dist/dashboard/routes/config.js +0 -54
- package/dist/dashboard/routes/extensions.js +0 -132
- package/dist/dashboard/routes/local-models.js +0 -68
- package/dist/dashboard/routes/models.js +0 -46
- package/dist/dashboard/routes/sessions.js +0 -100
- package/dist/dashboard/runtime.js +1 -58
- package/dist/dashboard/server.js +0 -49
- package/dist/dashboard/session-control.js +2 -69
- package/dist/model/catalog.js +1 -17
- package/dist/model/index.js +0 -15
- package/dist/model/injector.js +1 -181
- package/dist/model/provider-models.js +1 -48
- package/dist/model/responses-bridge.js +3 -45
- package/dist/model/store.js +0 -17
- package/dist/model/types.js +0 -12
- package/dist/model/validation.js +1 -18
- package/dist/pikichannel/adapter-pikiloom.js +0 -31
- package/dist/pikichannel/code.js +0 -12
- package/dist/pikichannel/codec.js +0 -14
- package/dist/pikichannel/host.js +4 -33
- package/dist/pikichannel/protocol.js +0 -52
- package/dist/pikichannel/rendezvous-broker.js +5 -26
- package/dist/pikichannel/rendezvous-host.js +3 -16
- package/dist/pikichannel/server.js +6 -55
- package/dist/pikichannel/transport.js +0 -25
- package/dist/pikichannel/transports/webrtc-host.js +0 -18
- package/dist/pikichannel/transports/webrtc-shared.js +4 -34
- package/dist/pikichannel/transports/websocket-host.js +1 -14
- package/dist/pikichannel/turn.js +2 -69
- package/dist/pikichannel/web/sdk.js +14 -71
- package/package.json +1 -1
- package/dashboard/dist/assets/ConnectionModal-Dt05H2f9.js +0 -1
- package/dashboard/dist/assets/SystemTab-CN_QrRKt.js +0 -1
- package/dashboard/dist/assets/index-CP3LYJR2.js +0 -23
- package/dashboard/dist/assets/index-C_neLnaO.js +0 -3
- 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;
|
|
34
|
+
return;
|
|
61
35
|
}
|
|
62
36
|
}
|
|
63
37
|
catch {
|
|
64
|
-
// Best-effort only — never block startup on migration.
|
|
65
38
|
}
|
|
66
39
|
}
|
package/dist/core/logging.js
CHANGED
package/dist/core/platform.js
CHANGED
|
@@ -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);
|
package/dist/core/secrets/ref.js
CHANGED
|
@@ -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;
|
|
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
|
}
|
package/dist/core/utils.js
CHANGED
|
@@ -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 {
|
|
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;
|
package/dist/core/version.js
CHANGED
|
@@ -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;
|
|
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'],
|