commandmate 0.1.12 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +4 -9
- package/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +24 -24
- package/.next/app-path-routes-manifest.json +1 -1
- package/.next/build-manifest.json +7 -7
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/1.pack +0 -0
- package/.next/cache/webpack/client-production/2.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/react-loadable-manifest.json +7 -7
- package/.next/required-server-files.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/api/hooks/claude-done/route.js +1 -19
- package/.next/server/app/api/hooks/claude-done/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
- package/.next/server/app/api/repositories/clone/[jobId]/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/clone/route.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/excluded/route.js +36 -0
- package/.next/server/app/api/repositories/excluded/route.js.nft.json +1 -0
- package/.next/server/app/api/repositories/excluded.body +1 -0
- package/.next/server/app/api/repositories/excluded.meta +1 -0
- package/.next/server/app/api/repositories/restore/route.js +36 -0
- package/.next/server/app/api/repositories/restore/route.js.nft.json +1 -0
- package/.next/server/app/api/repositories/route.js +36 -1
- package/.next/server/app/api/repositories/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/scan/route.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js +36 -1
- package/.next/server/app/api/slash-commands/route.js +1 -1
- package/.next/server/app/api/slash-commands.body +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/search/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +3 -3
- package/.next/server/app/page.js +7 -7
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/proxy/[...path]/route.js +2 -2
- package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/page.js +4 -4
- package/.next/server/app/worktrees/[id]/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/simple-terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +10 -8
- package/.next/server/chunks/5488.js +36 -0
- package/.next/server/chunks/6550.js +1 -1
- package/.next/server/chunks/7425.js +53 -50
- package/.next/server/chunks/7536.js +1 -0
- package/.next/server/chunks/8174.js +23 -0
- package/.next/server/chunks/9367.js +19 -0
- package/.next/server/functions-config-manifest.json +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-manifest.json +2 -28
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/4327.740cc7fe2d0b5049.js +60 -0
- package/.next/static/chunks/4343-ebe884a2a80eb033.js +1 -0
- package/.next/static/chunks/6568-38a33aa67d82e12b.js +1 -0
- package/.next/static/chunks/816-c254f4e2406e696a.js +1 -0
- package/.next/static/chunks/app/layout-4804cfba519283cf.js +1 -0
- package/.next/static/chunks/app/page-3926224c4cdf315b.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/page-d64624eb67af57c0.js +1 -0
- package/.next/static/chunks/main-b6d727aa9248d4f2.js +1 -0
- package/.next/static/chunks/{webpack-3fc79fab9bb738d7.js → webpack-4f85dcef6279c6ee.js} +1 -1
- package/.next/static/css/28be35e4727ae7ef.css +3 -0
- package/.next/trace +5 -5
- package/.next/types/app/api/repositories/excluded/route.ts +343 -0
- package/.next/types/app/api/repositories/restore/route.ts +343 -0
- package/README.md +2 -2
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +2 -13
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +3 -7
- package/dist/cli/config/security-messages.d.ts +11 -0
- package/dist/cli/config/security-messages.d.ts.map +1 -0
- package/dist/cli/config/security-messages.js +29 -0
- package/dist/cli/types/index.d.ts +0 -1
- package/dist/cli/types/index.d.ts.map +1 -1
- package/dist/cli/utils/daemon.d.ts.map +1 -1
- package/dist/cli/utils/daemon.js +3 -7
- package/dist/cli/utils/env-setup.d.ts +0 -4
- package/dist/cli/utils/env-setup.d.ts.map +1 -1
- package/dist/cli/utils/env-setup.js +0 -14
- package/dist/cli/utils/security-logger.d.ts.map +1 -1
- package/dist/cli/utils/security-logger.js +1 -2
- package/dist/server/src/lib/auto-yes-manager.js +13 -5
- package/dist/server/src/lib/claude-poller.js +337 -0
- package/dist/server/src/lib/cli-patterns.js +9 -2
- package/dist/server/src/lib/cli-tools/base.js +7 -1
- package/dist/server/src/lib/cli-tools/codex.js +14 -2
- package/dist/server/src/lib/cli-tools/manager.js +27 -0
- package/dist/server/src/lib/cli-tools/types.js +7 -0
- package/dist/server/src/lib/cli-tools/validation.js +41 -0
- package/dist/server/src/lib/db.js +23 -0
- package/dist/server/src/lib/env.js +0 -17
- package/dist/server/src/lib/logger.js +0 -4
- package/dist/server/src/lib/prompt-detector.js +129 -31
- package/dist/server/src/lib/ws-server.js +12 -1
- package/dist/server/src/types/sidebar.js +16 -31
- package/dist/server/src/types/slash-commands.js +2 -0
- package/package.json +1 -1
- package/.next/server/chunks/1318.js +0 -29
- package/.next/server/chunks/2597.js +0 -1
- package/.next/server/chunks/2648.js +0 -1
- package/.next/server/chunks/9703.js +0 -31
- package/.next/server/chunks/9723.js +0 -19
- package/.next/server/edge-runtime-webpack.js +0 -2
- package/.next/server/edge-runtime-webpack.js.map +0 -1
- package/.next/server/src/middleware.js +0 -14
- package/.next/server/src/middleware.js.map +0 -1
- package/.next/static/chunks/2853-d11a80b03c9a1640.js +0 -1
- package/.next/static/chunks/4327.3b84aa049900fdeb.js +0 -60
- package/.next/static/chunks/816-7e340dad784be28c.js +0 -1
- package/.next/static/chunks/9365-733d8c05712d2888.js +0 -1
- package/.next/static/chunks/app/layout-37e55f11dcc8b1bf.js +0 -1
- package/.next/static/chunks/app/page-fe35d61f14b90a51.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/page-58fcf2e63c056743.js +0 -1
- package/.next/static/chunks/main-a960f4a5e1a2f598.js +0 -1
- package/.next/static/css/376b339640084689.css +0 -3
- /package/.next/static/{564GHwluX5xIv9qpqLJV2 → bdUePCj-b9Gv5okYGp49O}/_buildManifest.js +0 -0
- /package/.next/static/{564GHwluX5xIv9qpqLJV2 → bdUePCj-b9Gv5okYGp49O}/_ssgManifest.js +0 -0
|
@@ -15,6 +15,7 @@ const preflight_1 = require("../utils/preflight");
|
|
|
15
15
|
const env_setup_1 = require("../utils/env-setup");
|
|
16
16
|
const prompt_1 = require("../utils/prompt");
|
|
17
17
|
const security_logger_1 = require("../utils/security-logger");
|
|
18
|
+
const security_messages_1 = require("../config/security-messages");
|
|
18
19
|
const logger = new logger_1.CLILogger();
|
|
19
20
|
/**
|
|
20
21
|
* Create default configuration (non-interactive mode)
|
|
@@ -66,15 +67,12 @@ async function promptForConfig() {
|
|
|
66
67
|
default: false,
|
|
67
68
|
});
|
|
68
69
|
let bind = env_setup_1.ENV_DEFAULTS.CM_BIND;
|
|
69
|
-
let authToken;
|
|
70
70
|
if (enableExternal) {
|
|
71
71
|
bind = '0.0.0.0';
|
|
72
|
-
const envSetup = new env_setup_1.EnvSetup();
|
|
73
|
-
authToken = envSetup.generateAuthToken();
|
|
74
72
|
logger.blank();
|
|
75
73
|
logger.success('External access enabled');
|
|
76
74
|
logger.info(` Bind address: 0.0.0.0`);
|
|
77
|
-
|
|
75
|
+
console.log(security_messages_1.REVERSE_PROXY_WARNING);
|
|
78
76
|
}
|
|
79
77
|
// CM_DB_PATH - Issue #135: Use getDefaultDbPath() for absolute path
|
|
80
78
|
const defaultDbPath = (0, env_setup_1.getDefaultDbPath)();
|
|
@@ -86,7 +84,6 @@ async function promptForConfig() {
|
|
|
86
84
|
CM_ROOT_DIR: rootDir,
|
|
87
85
|
CM_PORT: port,
|
|
88
86
|
CM_BIND: bind,
|
|
89
|
-
CM_AUTH_TOKEN: authToken,
|
|
90
87
|
CM_DB_PATH: dbPath,
|
|
91
88
|
CM_LOG_LEVEL: env_setup_1.ENV_DEFAULTS.CM_LOG_LEVEL,
|
|
92
89
|
CM_LOG_FORMAT: env_setup_1.ENV_DEFAULTS.CM_LOG_FORMAT,
|
|
@@ -105,18 +102,10 @@ function displayConfigSummary(config, envPath) {
|
|
|
105
102
|
logger.info(` CM_ROOT_DIR: ${config.CM_ROOT_DIR}`);
|
|
106
103
|
logger.info(` CM_PORT: ${config.CM_PORT}`);
|
|
107
104
|
logger.info(` CM_BIND: ${config.CM_BIND}`);
|
|
108
|
-
if (config.CM_AUTH_TOKEN) {
|
|
109
|
-
logger.info(` CM_AUTH_TOKEN: ${config.CM_AUTH_TOKEN.substring(0, 8)}... (generated)`);
|
|
110
|
-
}
|
|
111
105
|
logger.info(` CM_DB_PATH: ${config.CM_DB_PATH}`);
|
|
112
106
|
logger.blank();
|
|
113
107
|
logger.info(` Config file: ${envPath}`);
|
|
114
108
|
logger.blank();
|
|
115
|
-
if (config.CM_AUTH_TOKEN) {
|
|
116
|
-
logger.warn('IMPORTANT: Save your auth token securely!');
|
|
117
|
-
logger.info(` Token: ${config.CM_AUTH_TOKEN}`);
|
|
118
|
-
logger.blank();
|
|
119
|
-
}
|
|
120
109
|
}
|
|
121
110
|
/**
|
|
122
111
|
* Execute init command
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/start.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAAE,YAAY,EAA6B,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/start.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,EAAE,YAAY,EAA6B,MAAM,UAAU,CAAC;AAanE;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA8LvE"}
|
|
@@ -17,6 +17,7 @@ const daemon_1 = require("../utils/daemon");
|
|
|
17
17
|
const security_logger_1 = require("../utils/security-logger");
|
|
18
18
|
const paths_1 = require("../utils/paths");
|
|
19
19
|
const env_setup_1 = require("../utils/env-setup");
|
|
20
|
+
const security_messages_1 = require("../config/security-messages");
|
|
20
21
|
const input_validators_1 = require("../utils/input-validators");
|
|
21
22
|
const port_allocator_1 = require("../utils/port-allocator");
|
|
22
23
|
const resource_resolvers_1 = require("../utils/resource-resolvers");
|
|
@@ -139,15 +140,10 @@ async function startCommand(options) {
|
|
|
139
140
|
if (dbPath) {
|
|
140
141
|
env.CM_DB_PATH = dbPath;
|
|
141
142
|
}
|
|
142
|
-
// Issue #
|
|
143
|
+
// Issue #179: Security warning for external access - recommend reverse proxy
|
|
143
144
|
const bindAddress = env.CM_BIND || '127.0.0.1';
|
|
144
|
-
const authToken = env.CM_AUTH_TOKEN;
|
|
145
145
|
if (bindAddress === '0.0.0.0') {
|
|
146
|
-
|
|
147
|
-
if (!authToken) {
|
|
148
|
-
logger.warn('SECURITY WARNING: No authentication token configured. External access is not recommended without CM_AUTH_TOKEN.');
|
|
149
|
-
logger.info('Run "commandmate init" to configure a secure authentication token.');
|
|
150
|
-
}
|
|
146
|
+
console.log(security_messages_1.REVERSE_PROXY_WARNING);
|
|
151
147
|
}
|
|
152
148
|
// Use package installation directory, not current working directory
|
|
153
149
|
const packageRoot = (0, paths_1.getPackageRoot)();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Messages
|
|
3
|
+
* Issue #179: Reverse proxy authentication recommendation
|
|
4
|
+
* Shared warning constants for CLI commands (DRY principle)
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Warning message displayed when CM_BIND=0.0.0.0
|
|
8
|
+
* Used by: init.ts, start.ts, daemon.ts
|
|
9
|
+
*/
|
|
10
|
+
export declare const REVERSE_PROXY_WARNING = "\n\u001B[1m\u001B[31mWARNING: Server is exposed to external networks without authentication\u001B[0m\n\nExposing the server without reverse proxy authentication\nis a serious security risk.\n\n\u001B[1mRisks:\u001B[0m\n File read/write/delete and command execution\n become accessible to third parties.\n\n\u001B[1mRecommended authentication methods:\u001B[0m\n - Nginx + Basic Auth\n - Cloudflare Access\n - Tailscale\n\nDetails: https://github.com/Kewton/CommandMate/blob/main/docs/security-guide.md\n";
|
|
11
|
+
//# sourceMappingURL=security-messages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security-messages.d.ts","sourceRoot":"","sources":["../../../src/cli/config/security-messages.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;GAGG;AACH,eAAO,MAAM,qBAAqB,igBAgBjC,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Security Messages
|
|
4
|
+
* Issue #179: Reverse proxy authentication recommendation
|
|
5
|
+
* Shared warning constants for CLI commands (DRY principle)
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.REVERSE_PROXY_WARNING = void 0;
|
|
9
|
+
/**
|
|
10
|
+
* Warning message displayed when CM_BIND=0.0.0.0
|
|
11
|
+
* Used by: init.ts, start.ts, daemon.ts
|
|
12
|
+
*/
|
|
13
|
+
exports.REVERSE_PROXY_WARNING = `
|
|
14
|
+
\x1b[1m\x1b[31mWARNING: Server is exposed to external networks without authentication\x1b[0m
|
|
15
|
+
|
|
16
|
+
Exposing the server without reverse proxy authentication
|
|
17
|
+
is a serious security risk.
|
|
18
|
+
|
|
19
|
+
\x1b[1mRisks:\x1b[0m
|
|
20
|
+
File read/write/delete and command execution
|
|
21
|
+
become accessible to third parties.
|
|
22
|
+
|
|
23
|
+
\x1b[1mRecommended authentication methods:\x1b[0m
|
|
24
|
+
- Nginx + Basic Auth
|
|
25
|
+
- Cloudflare Access
|
|
26
|
+
- Tailscale
|
|
27
|
+
|
|
28
|
+
Details: https://github.com/Kewton/CommandMate/blob/main/docs/security-guide.md
|
|
29
|
+
`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,oBAAY,QAAQ;IAClB,OAAO,IAAI;IACX,gBAAgB,IAAI;IACpB,YAAY,IAAI;IAChB,YAAY,IAAI;IAChB,WAAW,IAAI;IACf,gBAAgB,KAAK;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,gCAAgC;IAChC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,wBAAwB;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,oCAAoC;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,QAAQ,EAAE,OAAO,CAAC;IAClB,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB;IACnB,MAAM,EAAE,IAAI,GAAG,SAAS,GAAG,kBAAkB,CAAC;IAC9C,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,sDAAsD;IACtD,OAAO,EAAE,OAAO,CAAC;IACjB,oCAAoC;IACpC,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,oBAAY,QAAQ;IAClB,OAAO,IAAI;IACX,gBAAgB,IAAI;IACpB,YAAY,IAAI;IAChB,YAAY,IAAI;IAChB,WAAW,IAAI;IACf,gBAAgB,KAAK;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,gCAAgC;IAChC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,wBAAwB;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,oCAAoC;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,QAAQ,EAAE,OAAO,CAAC;IAClB,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sBAAsB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,mBAAmB;IACnB,MAAM,EAAE,IAAI,GAAG,SAAS,GAAG,kBAAkB,CAAC;IAC9C,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,sDAAsD;IACtD,OAAO,EAAE,OAAO,CAAC;IACjB,oCAAoC;IACpC,OAAO,EAAE,gBAAgB,EAAE,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,oCAAoC;IACpC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CAC7C;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKtD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/daemon.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/daemon.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAOtD;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,MAAM,CAAY;gBAEd,WAAW,EAAE,MAAM;IAK/B;;;;;OAKG;IACG,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IA6EnD;;;;OAIG;IACG,IAAI,CAAC,KAAK,GAAE,OAAe,GAAG,OAAO,CAAC,OAAO,CAAC;IA8BpD;;;OAGG;IACG,SAAS,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IA6B/C;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAInC;;OAEG;YACW,WAAW;CAiB1B"}
|
package/dist/cli/utils/daemon.js
CHANGED
|
@@ -12,6 +12,7 @@ const dotenv_1 = require("dotenv");
|
|
|
12
12
|
const pid_manager_1 = require("./pid-manager");
|
|
13
13
|
const paths_1 = require("./paths");
|
|
14
14
|
const env_setup_1 = require("./env-setup");
|
|
15
|
+
const security_messages_1 = require("../config/security-messages");
|
|
15
16
|
const logger_1 = require("./logger");
|
|
16
17
|
/**
|
|
17
18
|
* Daemon manager for background server process
|
|
@@ -60,16 +61,11 @@ class DaemonManager {
|
|
|
60
61
|
if (options.dbPath) {
|
|
61
62
|
env.CM_DB_PATH = options.dbPath;
|
|
62
63
|
}
|
|
63
|
-
// Issue #
|
|
64
|
+
// Issue #179: Security warning for external access - recommend reverse proxy
|
|
64
65
|
const bindAddress = env.CM_BIND || '127.0.0.1';
|
|
65
|
-
const authToken = env.CM_AUTH_TOKEN;
|
|
66
66
|
const port = env.CM_PORT || '3000';
|
|
67
67
|
if (bindAddress === '0.0.0.0') {
|
|
68
|
-
|
|
69
|
-
if (!authToken) {
|
|
70
|
-
this.logger.warn('SECURITY WARNING: No authentication token configured. External access is not recommended without CM_AUTH_TOKEN.');
|
|
71
|
-
this.logger.info('Run "commandmate init" to configure a secure authentication token.');
|
|
72
|
-
}
|
|
68
|
+
console.log(security_messages_1.REVERSE_PROXY_WARNING);
|
|
73
69
|
}
|
|
74
70
|
// Log startup with accurate settings (Stage 4 review: MF-2)
|
|
75
71
|
this.logger.info(`Starting server at http://${bindAddress}:${port}`);
|
|
@@ -105,10 +105,6 @@ export declare class EnvSetup {
|
|
|
105
105
|
* Backup existing .env file
|
|
106
106
|
*/
|
|
107
107
|
backupExisting(): Promise<string | null>;
|
|
108
|
-
/**
|
|
109
|
-
* Generate secure authentication token
|
|
110
|
-
*/
|
|
111
|
-
generateAuthToken(): string;
|
|
112
108
|
/**
|
|
113
109
|
* Validate configuration
|
|
114
110
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-setup.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/env-setup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"env-setup.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/env-setup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,OAAO,EACL,SAAS,EACT,eAAe,EACf,gBAAgB,EACjB,MAAM,UAAU,CAAC;AASlB,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAElE;;;;;;GAMG;AACH,eAAO,MAAM,YAAY;;;;;CAMf,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,gBAAgB,QAA2B,CAAC;AAEzD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAOzC;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAwBnD;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CASpF;AAKD;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAGnC;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAYvD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGlD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUpD;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,CAAC,EAAE,MAAM;IAI5B;;;OAGG;IACG,aAAa,CACjB,MAAM,EAAE,SAAS,EACjB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC;IA6BhB;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAY9C;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,SAAS,GAAG,gBAAgB;CA+BpD"}
|
|
@@ -18,7 +18,6 @@ exports.validatePort = validatePort;
|
|
|
18
18
|
exports.escapeEnvValue = escapeEnvValue;
|
|
19
19
|
const fs_1 = require("fs");
|
|
20
20
|
const path_1 = require("path");
|
|
21
|
-
const crypto_1 = require("crypto");
|
|
22
21
|
const os_1 = require("os");
|
|
23
22
|
// Issue #136: Import from install-context.ts to avoid circular imports
|
|
24
23
|
// Re-export for backward compatibility
|
|
@@ -210,9 +209,6 @@ class EnvSetup {
|
|
|
210
209
|
`CM_LOG_LEVEL=${config.CM_LOG_LEVEL}`,
|
|
211
210
|
`CM_LOG_FORMAT=${config.CM_LOG_FORMAT}`,
|
|
212
211
|
];
|
|
213
|
-
if (config.CM_AUTH_TOKEN) {
|
|
214
|
-
lines.push(`CM_AUTH_TOKEN=${config.CM_AUTH_TOKEN}`);
|
|
215
|
-
}
|
|
216
212
|
lines.push('');
|
|
217
213
|
const content = lines.join('\n');
|
|
218
214
|
// Write with secure permissions
|
|
@@ -232,12 +228,6 @@ class EnvSetup {
|
|
|
232
228
|
(0, fs_1.copyFileSync)(this.envPath, backupPath);
|
|
233
229
|
return backupPath;
|
|
234
230
|
}
|
|
235
|
-
/**
|
|
236
|
-
* Generate secure authentication token
|
|
237
|
-
*/
|
|
238
|
-
generateAuthToken() {
|
|
239
|
-
return (0, crypto_1.randomBytes)(32).toString('hex');
|
|
240
|
-
}
|
|
241
231
|
/**
|
|
242
232
|
* Validate configuration
|
|
243
233
|
*/
|
|
@@ -252,10 +242,6 @@ class EnvSetup {
|
|
|
252
242
|
if (!validBinds.includes(config.CM_BIND)) {
|
|
253
243
|
errors.push(`Invalid bind address: must be one of ${validBinds.join(', ')}`);
|
|
254
244
|
}
|
|
255
|
-
// Require auth token for external access
|
|
256
|
-
if (config.CM_BIND === '0.0.0.0' && !config.CM_AUTH_TOKEN) {
|
|
257
|
-
errors.push('auth token is required when binding to 0.0.0.0');
|
|
258
|
-
}
|
|
259
245
|
// Validate log level
|
|
260
246
|
const validLogLevels = ['debug', 'info', 'warn', 'error'];
|
|
261
247
|
if (!validLogLevels.includes(config.CM_LOG_LEVEL)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security-logger.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/security-logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,oBAAoB;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,oBAAoB;IACpB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC1C,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAaD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAU3D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"security-logger.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/security-logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,oBAAoB;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,oBAAoB;IACpB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC1C,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAaD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAU3D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAY/E"}
|
|
@@ -43,9 +43,8 @@ function maskSensitiveData(input) {
|
|
|
43
43
|
if (!input) {
|
|
44
44
|
return input;
|
|
45
45
|
}
|
|
46
|
-
// Mask CM_AUTH_TOKEN values
|
|
47
|
-
let result = input.replace(/CM_AUTH_TOKEN=\S+/g, 'CM_AUTH_TOKEN=***masked***');
|
|
48
46
|
// Mask any token-like strings (12+ hex/alphanumeric characters after "token:")
|
|
47
|
+
let result = input;
|
|
49
48
|
result = result.replace(/(?:token|Token)[:\s]+([a-zA-Z0-9]{12,})/gi, (_match, token) => {
|
|
50
49
|
return _match.replace(token, '***');
|
|
51
50
|
});
|
|
@@ -205,22 +205,30 @@ async function pollAutoYes(worktreeId, cliToolId) {
|
|
|
205
205
|
try {
|
|
206
206
|
// 1. Capture tmux output
|
|
207
207
|
const output = await (0, cli_session_1.captureSessionOutput)(worktreeId, cliToolId, 5000);
|
|
208
|
-
// 2. Strip ANSI codes
|
|
208
|
+
// 2. Strip ANSI codes
|
|
209
209
|
const cleanOutput = (0, cli_patterns_1.stripAnsi)(output);
|
|
210
|
+
// 2.5. Skip prompt detection during thinking state (Issue #161, Layer 1)
|
|
211
|
+
// This prevents false positive detection of numbered lists in CLI output
|
|
212
|
+
// while Claude is actively processing (thinking/planning).
|
|
213
|
+
if ((0, cli_patterns_1.detectThinking)(cliToolId, cleanOutput)) {
|
|
214
|
+
scheduleNextPoll(worktreeId, cliToolId);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
// 3. Detect prompt
|
|
210
218
|
const promptDetection = (0, prompt_detector_1.detectPrompt)(cleanOutput);
|
|
211
219
|
if (!promptDetection.isPrompt || !promptDetection.promptData) {
|
|
212
220
|
// No prompt detected, schedule next poll
|
|
213
221
|
scheduleNextPoll(worktreeId, cliToolId);
|
|
214
222
|
return;
|
|
215
223
|
}
|
|
216
|
-
//
|
|
224
|
+
// 4. Resolve auto answer
|
|
217
225
|
const answer = (0, auto_yes_resolver_1.resolveAutoAnswer)(promptDetection.promptData);
|
|
218
226
|
if (answer === null) {
|
|
219
227
|
// Cannot auto-answer this prompt
|
|
220
228
|
scheduleNextPoll(worktreeId, cliToolId);
|
|
221
229
|
return;
|
|
222
230
|
}
|
|
223
|
-
//
|
|
231
|
+
// 5. Send answer to tmux
|
|
224
232
|
const manager = manager_1.CLIToolManager.getInstance();
|
|
225
233
|
const cliTool = manager.getTool(cliToolId);
|
|
226
234
|
const sessionName = cliTool.getSessionName(worktreeId);
|
|
@@ -228,9 +236,9 @@ async function pollAutoYes(worktreeId, cliToolId) {
|
|
|
228
236
|
await (0, tmux_1.sendKeys)(sessionName, answer, false);
|
|
229
237
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
230
238
|
await (0, tmux_1.sendKeys)(sessionName, '', true);
|
|
231
|
-
//
|
|
239
|
+
// 6. Update timestamp
|
|
232
240
|
updateLastServerResponseTimestamp(worktreeId, Date.now());
|
|
233
|
-
//
|
|
241
|
+
// 7. Reset error count on success
|
|
234
242
|
resetErrorCount(worktreeId);
|
|
235
243
|
// Log success (without sensitive content)
|
|
236
244
|
console.info(`[Auto-Yes Poller] Sent response for worktree: ${worktreeId}`);
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Claude response polling
|
|
4
|
+
* Periodically checks tmux sessions for Claude responses
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.startPolling = startPolling;
|
|
8
|
+
exports.stopPolling = stopPolling;
|
|
9
|
+
exports.stopAllPolling = stopAllPolling;
|
|
10
|
+
exports.getActivePollers = getActivePollers;
|
|
11
|
+
const claude_session_1 = require("./claude-session");
|
|
12
|
+
const db_instance_1 = require("./db-instance");
|
|
13
|
+
const db_1 = require("./db");
|
|
14
|
+
const ws_server_1 = require("./ws-server");
|
|
15
|
+
const prompt_detector_1 = require("./prompt-detector");
|
|
16
|
+
const conversation_logger_1 = require("./conversation-logger");
|
|
17
|
+
/**
|
|
18
|
+
* Polling interval in milliseconds (default: 2 seconds)
|
|
19
|
+
*/
|
|
20
|
+
const POLLING_INTERVAL = 2000;
|
|
21
|
+
/**
|
|
22
|
+
* Maximum polling duration in milliseconds (default: 5 minutes)
|
|
23
|
+
*/
|
|
24
|
+
const MAX_POLLING_DURATION = 5 * 60 * 1000;
|
|
25
|
+
/**
|
|
26
|
+
* Active pollers map: worktreeId -> NodeJS.Timeout
|
|
27
|
+
*/
|
|
28
|
+
const activePollers = new Map();
|
|
29
|
+
/**
|
|
30
|
+
* Polling start times map: worktreeId -> timestamp
|
|
31
|
+
*/
|
|
32
|
+
const pollingStartTimes = new Map();
|
|
33
|
+
/**
|
|
34
|
+
* Extract Claude response from tmux output
|
|
35
|
+
* Detects when Claude has completed a response by looking for the prompt
|
|
36
|
+
*
|
|
37
|
+
* @param output - Full tmux output
|
|
38
|
+
* @param lastCapturedLine - Number of lines previously captured
|
|
39
|
+
* @returns Extracted response or null if incomplete
|
|
40
|
+
*/
|
|
41
|
+
function extractClaudeResponse(output, lastCapturedLine) {
|
|
42
|
+
// Trim trailing empty lines from the output before processing
|
|
43
|
+
// This prevents the "last 20 lines" from being all empty due to tmux buffer padding
|
|
44
|
+
const rawLines = output.split('\n');
|
|
45
|
+
let trimmedLength = rawLines.length;
|
|
46
|
+
while (trimmedLength > 0 && rawLines[trimmedLength - 1].trim() === '') {
|
|
47
|
+
trimmedLength--;
|
|
48
|
+
}
|
|
49
|
+
const lines = rawLines.slice(0, trimmedLength);
|
|
50
|
+
const totalLines = lines.length;
|
|
51
|
+
// No new output (with buffer to handle newline inconsistencies)
|
|
52
|
+
if (totalLines < lastCapturedLine - 5) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
// Always check the last 20 lines for completion pattern (more robust than tracking line numbers)
|
|
56
|
+
const checkLineCount = 20;
|
|
57
|
+
const startLine = Math.max(0, totalLines - checkLineCount);
|
|
58
|
+
const linesToCheck = lines.slice(startLine);
|
|
59
|
+
const outputToCheck = linesToCheck.join('\n');
|
|
60
|
+
// Check if Claude has returned to prompt (indicated by the prompt symbols)
|
|
61
|
+
// Claude shows "> " or "❯ " or "─────" when waiting for input
|
|
62
|
+
// Supports both legacy '>' and new '❯' (U+276F) prompt characters
|
|
63
|
+
// Issue #132: Also matches prompts with recommended commands (e.g., "❯ /work-plan")
|
|
64
|
+
const promptPattern = /^[>❯](\s*$|\s+\S)/m;
|
|
65
|
+
const separatorPattern = /^─{50,}$/m;
|
|
66
|
+
// Check for thinking/processing indicators
|
|
67
|
+
// Claude shows various animations while thinking: ✻ Herding…, · Choreographing…, ∴ Thinking…, ✢ Doing…, ✳ Cascading…, etc.
|
|
68
|
+
// Match lines that contain: symbol + word + … OR just the symbol alone
|
|
69
|
+
const thinkingPattern = /[✻✽⏺·∴✢✳]/m;
|
|
70
|
+
const hasPrompt = promptPattern.test(outputToCheck);
|
|
71
|
+
const hasSeparator = separatorPattern.test(outputToCheck);
|
|
72
|
+
const isThinking = thinkingPattern.test(outputToCheck);
|
|
73
|
+
// Only consider complete if we have prompt + separator AND Claude is NOT thinking
|
|
74
|
+
if (hasPrompt && hasSeparator && !isThinking) {
|
|
75
|
+
// Claude has completed response
|
|
76
|
+
// Extract the response content from lastCapturedLine to the separator (not just last 20 lines)
|
|
77
|
+
const responseLines = [];
|
|
78
|
+
// Handle tmux buffer scrolling: if lastCapturedLine >= totalLines, the buffer has scrolled
|
|
79
|
+
// In this case, we need to find the response in the current visible buffer
|
|
80
|
+
let startIndex;
|
|
81
|
+
if (lastCapturedLine >= totalLines - 5) {
|
|
82
|
+
// Buffer may have scrolled - look for the start of the new response
|
|
83
|
+
// Find the last user input prompt ("> ...") to identify where the response starts
|
|
84
|
+
let foundUserPrompt = -1;
|
|
85
|
+
for (let i = totalLines - 1; i >= Math.max(0, totalLines - 50); i--) {
|
|
86
|
+
// Look for user input line (starts with "> " or "❯ " followed by content)
|
|
87
|
+
if (/^[>❯]\s+\S/.test(lines[i])) {
|
|
88
|
+
foundUserPrompt = i;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Start extraction from after the user prompt, or from a safe earlier point
|
|
93
|
+
startIndex = foundUserPrompt >= 0 ? foundUserPrompt + 1 : Math.max(0, totalLines - 40);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// Normal case: start from lastCapturedLine
|
|
97
|
+
startIndex = Math.max(0, lastCapturedLine);
|
|
98
|
+
}
|
|
99
|
+
for (let i = startIndex; i < totalLines; i++) {
|
|
100
|
+
const line = lines[i];
|
|
101
|
+
// Skip separator lines
|
|
102
|
+
if (/^─{50,}$/.test(line)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
// Stop at new prompt (supports both '>' and '❯')
|
|
106
|
+
if (/^[>❯]\s*$/.test(line)) {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
// Skip thinking/processing status lines (spinner char + activity text ending with …)
|
|
110
|
+
// Note: ⏺ is also used as a response marker, so we only skip if it looks like a thinking line
|
|
111
|
+
// Thinking line example: "✳ UIからジョブ再実行中… (esc to interrupt · 33m 44s · thinking)"
|
|
112
|
+
// Response line example: "⏺ 何かお手伝いできることはありますか?" (should NOT be skipped)
|
|
113
|
+
if (/[✻✽·∴✢✳⦿◉●○◌◎⊙⊚⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]\s*\S+…/.test(line) || /to interrupt\)/.test(line)) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
// Skip tip lines and decoration lines
|
|
117
|
+
if (/^\s*[⎿⏋]\s+Tip:/.test(line) || /^\s*Tip:/.test(line)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
responseLines.push(line);
|
|
121
|
+
}
|
|
122
|
+
const response = responseLines.join('\n').trim();
|
|
123
|
+
// Additional check: ensure response doesn't contain thinking indicators
|
|
124
|
+
// This prevents saving intermediate states as final responses
|
|
125
|
+
if (thinkingPattern.test(response)) {
|
|
126
|
+
console.warn(`[Poller] Response contains thinking indicators, treating as incomplete`);
|
|
127
|
+
return {
|
|
128
|
+
response: '',
|
|
129
|
+
isComplete: false,
|
|
130
|
+
lineCount: totalLines,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
response,
|
|
135
|
+
isComplete: true,
|
|
136
|
+
lineCount: totalLines,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// Check if this is an interactive prompt (yes/no or multiple choice)
|
|
140
|
+
// Interactive prompts don't have the ">" prompt and separator, so we need to detect them separately
|
|
141
|
+
if (!isThinking) {
|
|
142
|
+
const fullOutput = lines.join('\n');
|
|
143
|
+
const promptDetection = (0, prompt_detector_1.detectPrompt)(fullOutput);
|
|
144
|
+
if (promptDetection.isPrompt) {
|
|
145
|
+
// This is an interactive prompt - consider it complete
|
|
146
|
+
return {
|
|
147
|
+
response: fullOutput,
|
|
148
|
+
isComplete: true,
|
|
149
|
+
lineCount: totalLines,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Response not yet complete
|
|
154
|
+
return {
|
|
155
|
+
response: '',
|
|
156
|
+
isComplete: false,
|
|
157
|
+
lineCount: totalLines,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Check for Claude response once
|
|
162
|
+
*
|
|
163
|
+
* @param worktreeId - Worktree ID
|
|
164
|
+
* @returns True if response was found and processed
|
|
165
|
+
*/
|
|
166
|
+
async function checkForResponse(worktreeId) {
|
|
167
|
+
const db = (0, db_instance_1.getDbInstance)();
|
|
168
|
+
try {
|
|
169
|
+
// Get worktree to retrieve CLI tool ID
|
|
170
|
+
const worktree = (0, db_1.getWorktreeById)(db, worktreeId);
|
|
171
|
+
if (!worktree) {
|
|
172
|
+
console.error(`Worktree ${worktreeId} not found, stopping poller`);
|
|
173
|
+
stopPolling(worktreeId);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
const cliToolId = worktree.cliToolId || 'claude';
|
|
177
|
+
// Check if Claude session is running
|
|
178
|
+
const running = await (0, claude_session_1.isClaudeRunning)(worktreeId);
|
|
179
|
+
if (!running) {
|
|
180
|
+
console.log(`Claude session not running for ${worktreeId}, stopping poller`);
|
|
181
|
+
stopPolling(worktreeId);
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
// Get session state (last captured line count)
|
|
185
|
+
const sessionState = (0, db_1.getSessionState)(db, worktreeId, cliToolId);
|
|
186
|
+
const lastCapturedLine = sessionState?.lastCapturedLine || 0;
|
|
187
|
+
// Capture current output
|
|
188
|
+
const output = await (0, claude_session_1.captureClaudeOutput)(worktreeId, 10000);
|
|
189
|
+
// Extract response
|
|
190
|
+
const result = extractClaudeResponse(output, lastCapturedLine);
|
|
191
|
+
if (!result) {
|
|
192
|
+
// No new output
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
if (!result.isComplete) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
// Response is complete! Check if it's a prompt
|
|
199
|
+
const promptDetection = (0, prompt_detector_1.detectPrompt)(result.response);
|
|
200
|
+
if (promptDetection.isPrompt) {
|
|
201
|
+
// This is a prompt - save as prompt message
|
|
202
|
+
console.log(`✓ Detected prompt for ${worktreeId}:`, promptDetection.promptData?.question);
|
|
203
|
+
const message = (0, db_1.createMessage)(db, {
|
|
204
|
+
worktreeId,
|
|
205
|
+
role: 'assistant',
|
|
206
|
+
content: promptDetection.cleanContent,
|
|
207
|
+
messageType: 'prompt',
|
|
208
|
+
promptData: promptDetection.promptData,
|
|
209
|
+
timestamp: new Date(),
|
|
210
|
+
cliToolId,
|
|
211
|
+
});
|
|
212
|
+
// Update session state
|
|
213
|
+
(0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount);
|
|
214
|
+
// Broadcast to WebSocket
|
|
215
|
+
(0, ws_server_1.broadcastMessage)('message', {
|
|
216
|
+
worktreeId,
|
|
217
|
+
message,
|
|
218
|
+
});
|
|
219
|
+
console.log(`✓ Saved prompt message for ${worktreeId}`);
|
|
220
|
+
// Stop polling - waiting for user response
|
|
221
|
+
stopPolling(worktreeId);
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
// Normal response (not a prompt)
|
|
225
|
+
console.log(`✓ Detected Claude response for ${worktreeId}`);
|
|
226
|
+
// Validate response content is not empty
|
|
227
|
+
if (!result.response || result.response.trim() === '') {
|
|
228
|
+
console.warn(`⚠ Empty response detected for ${worktreeId}, continuing polling...`);
|
|
229
|
+
// Update session state but don't save the message
|
|
230
|
+
// Continue polling in case a prompt appears next
|
|
231
|
+
(0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount);
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
// Create Markdown log file for the conversation pair
|
|
235
|
+
if (result.response) {
|
|
236
|
+
await (0, conversation_logger_1.recordClaudeConversation)(db, worktreeId, result.response, 'claude');
|
|
237
|
+
}
|
|
238
|
+
// Create Claude message in database
|
|
239
|
+
const message = (0, db_1.createMessage)(db, {
|
|
240
|
+
worktreeId,
|
|
241
|
+
role: 'assistant',
|
|
242
|
+
content: result.response,
|
|
243
|
+
messageType: 'normal',
|
|
244
|
+
timestamp: new Date(),
|
|
245
|
+
cliToolId,
|
|
246
|
+
});
|
|
247
|
+
// Update session state
|
|
248
|
+
(0, db_1.updateSessionState)(db, worktreeId, cliToolId, result.lineCount);
|
|
249
|
+
// Broadcast message to WebSocket clients
|
|
250
|
+
(0, ws_server_1.broadcastMessage)('message', {
|
|
251
|
+
worktreeId,
|
|
252
|
+
message,
|
|
253
|
+
});
|
|
254
|
+
console.log(`✓ Saved Claude response for ${worktreeId}`);
|
|
255
|
+
// Stop polling since we got the response
|
|
256
|
+
stopPolling(worktreeId);
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
261
|
+
console.error(`Error checking for response (${worktreeId}):`, errorMessage);
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Start polling for Claude response
|
|
267
|
+
*
|
|
268
|
+
* @param worktreeId - Worktree ID
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```typescript
|
|
272
|
+
* startPolling('feature-foo');
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
275
|
+
function startPolling(worktreeId) {
|
|
276
|
+
// Stop existing poller if any
|
|
277
|
+
stopPolling(worktreeId);
|
|
278
|
+
console.log(`Starting poller for ${worktreeId}`);
|
|
279
|
+
// Record start time
|
|
280
|
+
pollingStartTimes.set(worktreeId, Date.now());
|
|
281
|
+
// Start polling
|
|
282
|
+
const interval = setInterval(async () => {
|
|
283
|
+
console.log(`[Poller] Checking for response: ${worktreeId}`);
|
|
284
|
+
const startTime = pollingStartTimes.get(worktreeId);
|
|
285
|
+
// Check if max duration exceeded
|
|
286
|
+
if (startTime && Date.now() - startTime > MAX_POLLING_DURATION) {
|
|
287
|
+
console.log(`Polling timeout for ${worktreeId}, stopping`);
|
|
288
|
+
stopPolling(worktreeId);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
// Check for response
|
|
292
|
+
try {
|
|
293
|
+
await checkForResponse(worktreeId);
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
console.error(`[Poller] Error in checkForResponse:`, error);
|
|
297
|
+
}
|
|
298
|
+
}, POLLING_INTERVAL);
|
|
299
|
+
activePollers.set(worktreeId, interval);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Stop polling for a worktree
|
|
303
|
+
*
|
|
304
|
+
* @param worktreeId - Worktree ID
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* ```typescript
|
|
308
|
+
* stopPolling('feature-foo');
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
function stopPolling(worktreeId) {
|
|
312
|
+
const interval = activePollers.get(worktreeId);
|
|
313
|
+
if (interval) {
|
|
314
|
+
clearInterval(interval);
|
|
315
|
+
activePollers.delete(worktreeId);
|
|
316
|
+
pollingStartTimes.delete(worktreeId);
|
|
317
|
+
console.log(`Stopped poller for ${worktreeId}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Stop all active pollers
|
|
322
|
+
* Used for cleanup on server shutdown
|
|
323
|
+
*/
|
|
324
|
+
function stopAllPolling() {
|
|
325
|
+
console.log(`Stopping all pollers (${activePollers.size} active)`);
|
|
326
|
+
for (const worktreeId of activePollers.keys()) {
|
|
327
|
+
stopPolling(worktreeId);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Get list of active pollers
|
|
332
|
+
*
|
|
333
|
+
* @returns Array of worktree IDs currently being polled
|
|
334
|
+
*/
|
|
335
|
+
function getActivePollers() {
|
|
336
|
+
return Array.from(activePollers.keys());
|
|
337
|
+
}
|