quickclaude 1.0.8 → 1.1.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/.github/workflows/publish.yml +7 -4
- package/CLAUDE.md +27 -0
- package/README.md +5 -0
- package/bin/quickclaude.js +28 -6
- package/package.json +1 -1
|
@@ -2,8 +2,8 @@ name: Publish to npm
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
|
|
6
|
-
-
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
7
|
|
|
8
8
|
jobs:
|
|
9
9
|
publish:
|
|
@@ -12,11 +12,14 @@ jobs:
|
|
|
12
12
|
contents: read
|
|
13
13
|
id-token: write
|
|
14
14
|
steps:
|
|
15
|
-
- uses: actions/checkout@
|
|
16
|
-
- uses: actions/setup-node@
|
|
15
|
+
- uses: actions/checkout@v5
|
|
16
|
+
- uses: actions/setup-node@v5
|
|
17
17
|
with:
|
|
18
18
|
node-version: 22
|
|
19
|
+
registry-url: https://registry.npmjs.org
|
|
19
20
|
- run: npm install -g npm@latest
|
|
20
21
|
- run: npm --version
|
|
21
22
|
- run: npm ci
|
|
22
23
|
- run: npm publish --provenance --access public
|
|
24
|
+
env:
|
|
25
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
quickclaude is a CLI launcher for Claude Code. It scans `~/.claude/projects/` for project directories, presents an interactive selection menu, and spawns `claude` in the chosen directory.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
- **Run**: `node bin/quickclaude.js` or `npm start`
|
|
12
|
+
- **Install globally (for testing)**: `npm install -g .`
|
|
13
|
+
- **No build step or test suite** — this is a single-file Node.js CLI tool
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
The entire tool lives in `bin/quickclaude.js` (ESM, `#!/usr/bin/env node`):
|
|
18
|
+
|
|
19
|
+
- **`resolvePath(encoded)`** — Converts Claude's encoded project directory names (e.g., `-Users-dongwoo-projects-my-app`) back to real filesystem paths. Uses greedy longest-match against the filesystem, trying both `-` and space as segment joiners. Handles Windows drive letters (`C--Users-...`).
|
|
20
|
+
- **`getProjects()`** — Reads `~/.claude/projects/`, resolves each subdirectory name via `resolvePath`, filters out worktree dirs and non-existent paths.
|
|
21
|
+
- **`main()`** — Uses `@clack/prompts` for the interactive select UI, then spawns `claude` with `stdio: "inherit"` in the selected directory.
|
|
22
|
+
|
|
23
|
+
## Key Details
|
|
24
|
+
|
|
25
|
+
- Single dependency: `@clack/prompts` for terminal UI
|
|
26
|
+
- Published to npm as `quickclaude` (global install / npx)
|
|
27
|
+
- Requires Node.js 18+ and Claude Code installed
|
package/README.md
CHANGED
package/bin/quickclaude.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import * as p from "@clack/prompts";
|
|
4
|
-
import { readdirSync, existsSync } from "fs";
|
|
4
|
+
import { readdirSync, existsSync, statSync } from "fs";
|
|
5
5
|
import { join, sep } from "path";
|
|
6
6
|
import { homedir } from "os";
|
|
7
7
|
import { execSync, spawn } from "child_process";
|
|
@@ -68,13 +68,33 @@ function getProjects() {
|
|
|
68
68
|
if (p.path.includes(`claude${sep}worktrees`)) return false;
|
|
69
69
|
return existsSync(p.path);
|
|
70
70
|
})
|
|
71
|
-
.
|
|
71
|
+
.map((p) => {
|
|
72
|
+
const projectDir = join(CLAUDE_PROJECTS_DIR, p.dirName);
|
|
73
|
+
const mtime = statSync(projectDir).mtimeMs;
|
|
74
|
+
return { ...p, mtime };
|
|
75
|
+
})
|
|
76
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function timeAgo(mtimeMs) {
|
|
80
|
+
const seconds = Math.floor((Date.now() - mtimeMs) / 1000);
|
|
81
|
+
if (seconds < 60) return "just now";
|
|
82
|
+
const minutes = Math.floor(seconds / 60);
|
|
83
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
84
|
+
const hours = Math.floor(minutes / 60);
|
|
85
|
+
if (hours < 24) return `${hours}h ago`;
|
|
86
|
+
const days = Math.floor(hours / 24);
|
|
87
|
+
if (days < 7) return `${days}d ago`;
|
|
88
|
+
const weeks = Math.floor(days / 7);
|
|
89
|
+
if (weeks < 4) return `${weeks}w ago`;
|
|
90
|
+
const months = Math.floor(days / 30);
|
|
91
|
+
return `${months}mo ago`;
|
|
72
92
|
}
|
|
73
93
|
|
|
74
|
-
function getProjectLabel(path) {
|
|
94
|
+
function getProjectLabel(path, mtimeMs) {
|
|
75
95
|
const home = homedir();
|
|
76
96
|
const display = path.startsWith(home) ? "~" + path.slice(home.length) : path;
|
|
77
|
-
return display
|
|
97
|
+
return `${timeAgo(mtimeMs)} · ${display}`;
|
|
78
98
|
}
|
|
79
99
|
|
|
80
100
|
async function main() {
|
|
@@ -91,7 +111,7 @@ async function main() {
|
|
|
91
111
|
message: "Select a project",
|
|
92
112
|
options: projects.map((proj) => ({
|
|
93
113
|
value: proj.path,
|
|
94
|
-
label: getProjectLabel(proj.path),
|
|
114
|
+
label: getProjectLabel(proj.path, proj.mtime),
|
|
95
115
|
})),
|
|
96
116
|
});
|
|
97
117
|
|
|
@@ -100,10 +120,12 @@ async function main() {
|
|
|
100
120
|
process.exit(0);
|
|
101
121
|
}
|
|
102
122
|
|
|
123
|
+
const args = process.argv.slice(2);
|
|
124
|
+
|
|
103
125
|
p.outro(`Launching Claude in ${selected}`);
|
|
104
126
|
|
|
105
127
|
// claude 실행 (현재 터미널에서 interactive하게)
|
|
106
|
-
const child = spawn("claude",
|
|
128
|
+
const child = spawn("claude", args, {
|
|
107
129
|
cwd: selected,
|
|
108
130
|
stdio: "inherit",
|
|
109
131
|
shell: true,
|