claude-code-workflow 6.0.2 → 6.0.4

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.
@@ -1,14 +1,105 @@
1
1
  import { serveCommand } from './serve.js';
2
+ import { launchBrowser } from '../utils/browser-launcher.js';
3
+ import { validatePath } from '../utils/path-resolver.js';
4
+ import chalk from 'chalk';
2
5
 
3
6
  /**
4
- * View command handler - starts dashboard server (unified with serve mode)
7
+ * Check if server is already running on the specified port
8
+ * @param {number} port - Port to check
9
+ * @returns {Promise<boolean>} True if server is running
10
+ */
11
+ async function isServerRunning(port) {
12
+ try {
13
+ const controller = new AbortController();
14
+ const timeoutId = setTimeout(() => controller.abort(), 1000);
15
+
16
+ const response = await fetch(`http://localhost:${port}/api/health`, {
17
+ signal: controller.signal
18
+ });
19
+ clearTimeout(timeoutId);
20
+
21
+ return response.ok;
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Switch workspace on running server
29
+ * @param {number} port - Server port
30
+ * @param {string} path - New workspace path
31
+ * @returns {Promise<Object>} Result with success status
32
+ */
33
+ async function switchWorkspace(port, path) {
34
+ try {
35
+ const response = await fetch(
36
+ `http://localhost:${port}/api/switch-path?path=${encodeURIComponent(path)}`
37
+ );
38
+ return await response.json();
39
+ } catch (err) {
40
+ return { success: false, error: err.message };
41
+ }
42
+ }
43
+
44
+ /**
45
+ * View command handler - opens dashboard for current workspace
46
+ * If server is already running, switches workspace and opens browser
47
+ * If not running, starts a new server
5
48
  * @param {Object} options - Command options
6
49
  */
7
50
  export async function viewCommand(options) {
8
- // Forward to serve command with same options
9
- await serveCommand({
10
- path: options.path,
11
- port: options.port || 3456,
12
- browser: options.browser
13
- });
51
+ const port = options.port || 3456;
52
+
53
+ // Resolve workspace path
54
+ let workspacePath = process.cwd();
55
+ if (options.path) {
56
+ const pathValidation = validatePath(options.path, { mustExist: true });
57
+ if (!pathValidation.valid) {
58
+ console.error(chalk.red(`\n Error: ${pathValidation.error}\n`));
59
+ process.exit(1);
60
+ }
61
+ workspacePath = pathValidation.path;
62
+ }
63
+
64
+ // Check if server is already running
65
+ const serverRunning = await isServerRunning(port);
66
+
67
+ if (serverRunning) {
68
+ // Server is running - switch workspace and open browser
69
+ console.log(chalk.blue.bold('\n CCW Dashboard\n'));
70
+ console.log(chalk.gray(` Server already running on port ${port}`));
71
+ console.log(chalk.cyan(` Switching workspace to: ${workspacePath}`));
72
+
73
+ const result = await switchWorkspace(port, workspacePath);
74
+
75
+ if (result.success) {
76
+ console.log(chalk.green(` Workspace switched successfully`));
77
+
78
+ // Open browser with the new path
79
+ const url = `http://localhost:${port}/?path=${encodeURIComponent(result.path)}`;
80
+
81
+ if (options.browser !== false) {
82
+ console.log(chalk.cyan(' Opening in browser...'));
83
+ try {
84
+ await launchBrowser(url);
85
+ console.log(chalk.green.bold('\n Dashboard opened!\n'));
86
+ } catch (err) {
87
+ console.log(chalk.yellow(`\n Could not open browser: ${err.message}`));
88
+ console.log(chalk.gray(` Open manually: ${url}\n`));
89
+ }
90
+ } else {
91
+ console.log(chalk.gray(`\n URL: ${url}\n`));
92
+ }
93
+ } else {
94
+ console.error(chalk.red(`\n Failed to switch workspace: ${result.error}\n`));
95
+ process.exit(1);
96
+ }
97
+ } else {
98
+ // Server not running - start new server
99
+ await serveCommand({
100
+ path: workspacePath,
101
+ port: port,
102
+ browser: options.browser
103
+ });
104
+ }
14
105
  }
@@ -126,6 +126,40 @@ export async function startServer(options = {}) {
126
126
  return;
127
127
  }
128
128
 
129
+ // API: Switch workspace path (for ccw view command)
130
+ if (pathname === '/api/switch-path') {
131
+ const newPath = url.searchParams.get('path');
132
+ if (!newPath) {
133
+ res.writeHead(400, { 'Content-Type': 'application/json' });
134
+ res.end(JSON.stringify({ error: 'Path is required' }));
135
+ return;
136
+ }
137
+
138
+ const resolved = resolvePath(newPath);
139
+ if (!existsSync(resolved)) {
140
+ res.writeHead(404, { 'Content-Type': 'application/json' });
141
+ res.end(JSON.stringify({ error: 'Path does not exist' }));
142
+ return;
143
+ }
144
+
145
+ // Track the path and return success
146
+ trackRecentPath(resolved);
147
+ res.writeHead(200, { 'Content-Type': 'application/json' });
148
+ res.end(JSON.stringify({
149
+ success: true,
150
+ path: resolved,
151
+ recentPaths: getRecentPaths()
152
+ }));
153
+ return;
154
+ }
155
+
156
+ // API: Health check (for ccw view to detect running server)
157
+ if (pathname === '/api/health') {
158
+ res.writeHead(200, { 'Content-Type': 'application/json' });
159
+ res.end(JSON.stringify({ status: 'ok', timestamp: Date.now() }));
160
+ return;
161
+ }
162
+
129
163
  // API: Remove a recent path
130
164
  if (pathname === '/api/remove-recent-path' && req.method === 'POST') {
131
165
  handlePostRequest(req, res, async (body) => {
@@ -20,7 +20,17 @@ document.addEventListener('DOMContentLoaded', async () => {
20
20
  // Server mode: load data from API
21
21
  try {
22
22
  if (window.SERVER_MODE) {
23
- await switchToPath(window.INITIAL_PATH || projectPath);
23
+ // Check URL for path parameter (from ccw view command)
24
+ const urlParams = new URLSearchParams(window.location.search);
25
+ const urlPath = urlParams.get('path');
26
+ const initialPath = urlPath || window.INITIAL_PATH || projectPath;
27
+
28
+ await switchToPath(initialPath);
29
+
30
+ // Clean up URL after loading (remove query param)
31
+ if (urlPath && window.history.replaceState) {
32
+ window.history.replaceState({}, '', window.location.pathname);
33
+ }
24
34
  } else {
25
35
  renderDashboard();
26
36
  }
@@ -7929,3 +7929,186 @@ code.ctx-meta-chip-value {
7929
7929
  width: 16px;
7930
7930
  border-radius: 3px;
7931
7931
  }
7932
+
7933
+ /* ===================================
7934
+ Path Selection Modal
7935
+ =================================== */
7936
+
7937
+ .path-modal-overlay {
7938
+ position: fixed;
7939
+ top: 0;
7940
+ left: 0;
7941
+ right: 0;
7942
+ bottom: 0;
7943
+ background: rgba(0, 0, 0, 0.5);
7944
+ display: flex;
7945
+ align-items: center;
7946
+ justify-content: center;
7947
+ z-index: 1000;
7948
+ backdrop-filter: blur(2px);
7949
+ }
7950
+
7951
+ .path-modal {
7952
+ background: hsl(var(--card));
7953
+ border: 1px solid hsl(var(--border));
7954
+ border-radius: 0.75rem;
7955
+ width: 90%;
7956
+ max-width: 480px;
7957
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
7958
+ animation: modal-enter 0.2s ease-out;
7959
+ }
7960
+
7961
+ @keyframes modal-enter {
7962
+ from {
7963
+ opacity: 0;
7964
+ transform: scale(0.95) translateY(-10px);
7965
+ }
7966
+ to {
7967
+ opacity: 1;
7968
+ transform: scale(1) translateY(0);
7969
+ }
7970
+ }
7971
+
7972
+ .path-modal-header {
7973
+ display: flex;
7974
+ align-items: center;
7975
+ gap: 0.75rem;
7976
+ padding: 1.25rem 1.5rem;
7977
+ border-bottom: 1px solid hsl(var(--border));
7978
+ }
7979
+
7980
+ .path-modal-icon {
7981
+ font-size: 1.5rem;
7982
+ color: hsl(var(--primary));
7983
+ }
7984
+
7985
+ .path-modal-header h3 {
7986
+ margin: 0;
7987
+ font-size: 1.1rem;
7988
+ font-weight: 600;
7989
+ color: hsl(var(--foreground));
7990
+ }
7991
+
7992
+ .path-modal-body {
7993
+ padding: 1.5rem;
7994
+ }
7995
+
7996
+ .path-modal-body p {
7997
+ margin: 0 0 1rem;
7998
+ color: hsl(var(--muted-foreground));
7999
+ font-size: 0.9rem;
8000
+ line-height: 1.5;
8001
+ }
8002
+
8003
+ .path-modal-command {
8004
+ display: flex;
8005
+ align-items: center;
8006
+ gap: 0.75rem;
8007
+ background: hsl(var(--muted));
8008
+ padding: 0.75rem 1rem;
8009
+ border-radius: 0.5rem;
8010
+ font-family: var(--font-mono);
8011
+ }
8012
+
8013
+ .path-modal-command code {
8014
+ flex: 1;
8015
+ font-size: 0.85rem;
8016
+ color: hsl(var(--foreground));
8017
+ word-break: break-all;
8018
+ }
8019
+
8020
+ .path-modal-command .copy-btn {
8021
+ display: flex;
8022
+ align-items: center;
8023
+ gap: 0.375rem;
8024
+ padding: 0.375rem 0.75rem;
8025
+ background: hsl(var(--primary));
8026
+ color: white;
8027
+ border: none;
8028
+ border-radius: 0.375rem;
8029
+ font-size: 0.8rem;
8030
+ cursor: pointer;
8031
+ transition: all 0.15s;
8032
+ white-space: nowrap;
8033
+ }
8034
+
8035
+ .path-modal-command .copy-btn:hover {
8036
+ background: hsl(var(--primary) / 0.9);
8037
+ }
8038
+
8039
+ .path-modal-note {
8040
+ font-size: 0.85rem !important;
8041
+ color: hsl(var(--muted-foreground)) !important;
8042
+ }
8043
+
8044
+ .path-modal-note code {
8045
+ background: hsl(var(--muted));
8046
+ padding: 0.125rem 0.375rem;
8047
+ border-radius: 0.25rem;
8048
+ font-size: 0.8rem;
8049
+ }
8050
+
8051
+ .path-modal-input {
8052
+ width: 100%;
8053
+ padding: 0.75rem 1rem;
8054
+ background: hsl(var(--background));
8055
+ border: 1px solid hsl(var(--border));
8056
+ border-radius: 0.5rem;
8057
+ font-size: 0.9rem;
8058
+ color: hsl(var(--foreground));
8059
+ outline: none;
8060
+ transition: border-color 0.15s;
8061
+ }
8062
+
8063
+ .path-modal-input:focus {
8064
+ border-color: hsl(var(--primary));
8065
+ }
8066
+
8067
+ .path-modal-input::placeholder {
8068
+ color: hsl(var(--muted-foreground));
8069
+ }
8070
+
8071
+ .path-modal-footer {
8072
+ display: flex;
8073
+ justify-content: flex-end;
8074
+ gap: 0.75rem;
8075
+ padding: 1rem 1.5rem;
8076
+ border-top: 1px solid hsl(var(--border));
8077
+ background: hsl(var(--muted) / 0.3);
8078
+ border-radius: 0 0 0.75rem 0.75rem;
8079
+ }
8080
+
8081
+ .path-modal-close {
8082
+ padding: 0.5rem 1.25rem;
8083
+ background: hsl(var(--muted));
8084
+ color: hsl(var(--foreground));
8085
+ border: none;
8086
+ border-radius: 0.375rem;
8087
+ font-size: 0.875rem;
8088
+ cursor: pointer;
8089
+ transition: all 0.15s;
8090
+ }
8091
+
8092
+ .path-modal-close:hover {
8093
+ background: hsl(var(--hover));
8094
+ }
8095
+
8096
+ .path-modal-confirm {
8097
+ padding: 0.5rem 1.25rem;
8098
+ background: hsl(var(--primary));
8099
+ color: white;
8100
+ border: none;
8101
+ border-radius: 0.375rem;
8102
+ font-size: 0.875rem;
8103
+ cursor: pointer;
8104
+ transition: all 0.15s;
8105
+ }
8106
+
8107
+ .path-modal-confirm:hover {
8108
+ background: hsl(var(--primary) / 0.9);
8109
+ }
8110
+
8111
+ .path-modal-confirm:disabled {
8112
+ opacity: 0.5;
8113
+ cursor: not-allowed;
8114
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-workflow",
3
- "version": "6.0.2",
3
+ "version": "6.0.4",
4
4
  "description": "JSON-driven multi-agent development framework with intelligent CLI orchestration (Gemini/Qwen/Codex), context-first architecture, and automated workflow execution",
5
5
  "type": "module",
6
6
  "main": "ccw/src/index.js",