pipit-cli 0.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/LICENSE +21 -0
- package/README.md +118 -0
- package/dist/cli.js +729 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Maximilian Maksutovic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# pipit
|
|
2
|
+
|
|
3
|
+
Pipe any context into Claude Code sessions.
|
|
4
|
+
|
|
5
|
+
Screenshots, meeting notes, URLs, clipboard text -- capture it in one command, and Claude Code extracts tasks, plans implementation, and gets to work.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
pipit go notes.md my-project
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
That's it. pipit opens a new terminal tab, feeds your context to Claude Code with a carefully crafted prompt, and Claude handles the rest -- extracting action items, planning, and implementing.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g pipit-cli
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requires **Node.js 20+** and [Claude Code](https://claude.ai/code) installed.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### `pipit go` -- send context to Claude Code
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# File (text, markdown, transcript)
|
|
27
|
+
pipit go meeting-notes.md my-project
|
|
28
|
+
|
|
29
|
+
# Screenshot or image
|
|
30
|
+
pipit go screenshot.png my-project
|
|
31
|
+
|
|
32
|
+
# URL
|
|
33
|
+
pipit go https://example.com/spec my-project
|
|
34
|
+
|
|
35
|
+
# Stdin
|
|
36
|
+
cat notes.txt | pipit go - my-project
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
pipit infers the input type automatically. Override with `--input-type` if needed.
|
|
40
|
+
|
|
41
|
+
### Options
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
pipit go <input> <project-name> [options]
|
|
45
|
+
|
|
46
|
+
Options:
|
|
47
|
+
-p, --project-dir <dir> Project directory (repeatable)
|
|
48
|
+
--input-type <type> Override input type: file, image, url, stdin
|
|
49
|
+
--control-level <level> auto (default), plan_first, extract_only
|
|
50
|
+
--mode <mode> fire_and_forget (default), interactive
|
|
51
|
+
--terminal <name> ghostty, iterm2, warp, terminal
|
|
52
|
+
--context <text> Additional instructions for Claude
|
|
53
|
+
--dry-run Print the prompt without launching
|
|
54
|
+
--json Machine-readable JSON output
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Control levels
|
|
58
|
+
|
|
59
|
+
- **auto** -- Claude gets full autonomy (`--dangerously-skip-permissions`). Context goes in, code comes out.
|
|
60
|
+
- **plan_first** -- Claude extracts and plans, then pauses for your approval before implementing.
|
|
61
|
+
- **extract_only** -- Just show the extracted tasks. You decide what to do next.
|
|
62
|
+
|
|
63
|
+
### Session modes
|
|
64
|
+
|
|
65
|
+
- **fire_and_forget** -- Claude works autonomously. You watch and intervene if needed.
|
|
66
|
+
- **interactive** -- Claude interviews you first, then plans and implements collaboratively.
|
|
67
|
+
|
|
68
|
+
## How it works
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
Your context (file, image, URL, clipboard)
|
|
72
|
+
|
|
|
73
|
+
v
|
|
74
|
+
pipit CLI (builds prompt, resolves input)
|
|
75
|
+
|
|
|
76
|
+
v
|
|
77
|
+
Terminal tab (Ghostty, iTerm2, Warp, Terminal.app)
|
|
78
|
+
|
|
|
79
|
+
v
|
|
80
|
+
Claude Code (extracts tasks, plans, implements, commits)
|
|
81
|
+
|
|
|
82
|
+
v
|
|
83
|
+
Results (branches, commits, PRs)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
pipit doesn't preprocess your input or run a separate extraction pipeline. It builds a prompt and hands everything to Claude Code in an interactive terminal session. Claude is smart enough to handle raw context directly.
|
|
87
|
+
|
|
88
|
+
## Terminal support
|
|
89
|
+
|
|
90
|
+
pipit opens Claude Code sessions in new terminal tabs via AppleScript:
|
|
91
|
+
|
|
92
|
+
- **Ghostty** (default) -- native API
|
|
93
|
+
- **iTerm2** -- AppleScript integration
|
|
94
|
+
- **Warp** -- AppleScript integration
|
|
95
|
+
- **Terminal.app** -- always available as fallback
|
|
96
|
+
|
|
97
|
+
Set the default with `--terminal` or the `PIPIT_TERMINAL` environment variable.
|
|
98
|
+
|
|
99
|
+
## Mac app
|
|
100
|
+
|
|
101
|
+
pipit also has a native macOS menu bar app that provides:
|
|
102
|
+
|
|
103
|
+
- Global hotkey to capture clipboard and launch a session
|
|
104
|
+
- File watchers for screenshots and transcripts
|
|
105
|
+
- Project management with multi-directory support
|
|
106
|
+
- One-click CLI installation
|
|
107
|
+
|
|
108
|
+
The Mac app calls the CLI under the hood -- all the logic lives here.
|
|
109
|
+
|
|
110
|
+
## Requirements
|
|
111
|
+
|
|
112
|
+
- **Node.js 20+**
|
|
113
|
+
- **Claude Code** CLI installed and authenticated
|
|
114
|
+
- **macOS** (terminal automation uses AppleScript)
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command, Option } from "commander";
|
|
5
|
+
import { access as access2 } from "fs/promises";
|
|
6
|
+
import { resolve as resolve3 } from "path";
|
|
7
|
+
|
|
8
|
+
// src/extractor/extractor.ts
|
|
9
|
+
import { readFile } from "fs/promises";
|
|
10
|
+
import { resolve } from "path";
|
|
11
|
+
import { execa } from "execa";
|
|
12
|
+
|
|
13
|
+
// src/extractor/types.ts
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
var ActionItemSchema = z.object({
|
|
16
|
+
id: z.string(),
|
|
17
|
+
title: z.string(),
|
|
18
|
+
description: z.string(),
|
|
19
|
+
priority: z.enum(["high", "medium", "low"]),
|
|
20
|
+
estimated_complexity: z.enum(["trivial", "small", "medium", "large"]),
|
|
21
|
+
relevant_files: z.array(z.string()),
|
|
22
|
+
transcript_context: z.string()
|
|
23
|
+
});
|
|
24
|
+
var ExtractionResultSchema = z.object({
|
|
25
|
+
meeting_summary: z.string(),
|
|
26
|
+
action_items: z.array(ActionItemSchema),
|
|
27
|
+
decisions: z.array(z.string()),
|
|
28
|
+
questions_to_clarify: z.array(z.string()),
|
|
29
|
+
claude_md_updates: z.array(z.string())
|
|
30
|
+
});
|
|
31
|
+
function getExtractionJsonSchema() {
|
|
32
|
+
return JSON.stringify({
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: {
|
|
35
|
+
meeting_summary: { type: "string" },
|
|
36
|
+
action_items: {
|
|
37
|
+
type: "array",
|
|
38
|
+
items: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
id: { type: "string" },
|
|
42
|
+
title: { type: "string" },
|
|
43
|
+
description: { type: "string" },
|
|
44
|
+
priority: { type: "string", enum: ["high", "medium", "low"] },
|
|
45
|
+
estimated_complexity: { type: "string", enum: ["trivial", "small", "medium", "large"] },
|
|
46
|
+
relevant_files: { type: "array", items: { type: "string" } },
|
|
47
|
+
transcript_context: { type: "string" }
|
|
48
|
+
},
|
|
49
|
+
required: ["id", "title", "description", "priority", "estimated_complexity", "relevant_files", "transcript_context"]
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
decisions: { type: "array", items: { type: "string" } },
|
|
53
|
+
questions_to_clarify: { type: "array", items: { type: "string" } },
|
|
54
|
+
claude_md_updates: { type: "array", items: { type: "string" } }
|
|
55
|
+
},
|
|
56
|
+
required: ["meeting_summary", "action_items", "decisions", "questions_to_clarify", "claude_md_updates"]
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/extractor/extractor.ts
|
|
61
|
+
var EXTRACTION_SYSTEM_PROMPT = `You are analyzing a meeting transcript to extract actionable development tasks.
|
|
62
|
+
|
|
63
|
+
For each task you identify:
|
|
64
|
+
- Give it a unique id (use kebab-case, e.g. "add-user-auth", "fix-api-timeout")
|
|
65
|
+
- Write a clear title and detailed description of what needs to be implemented
|
|
66
|
+
- Assess priority (high/medium/low) based on urgency signals in the conversation
|
|
67
|
+
- Estimate complexity (trivial/small/medium/large) based on scope
|
|
68
|
+
- List any files mentioned or implied as relevant
|
|
69
|
+
- Include the relevant transcript excerpt that prompted this task
|
|
70
|
+
|
|
71
|
+
Also extract:
|
|
72
|
+
- A brief meeting summary
|
|
73
|
+
- Key decisions made during the meeting
|
|
74
|
+
- Questions that need clarification before implementation
|
|
75
|
+
- Any updates that should be made to the project's CLAUDE.md`;
|
|
76
|
+
async function extractTasks(transcriptPath, projectDir) {
|
|
77
|
+
const absolutePath = resolve(transcriptPath);
|
|
78
|
+
const transcript = await readFile(absolutePath, "utf-8");
|
|
79
|
+
const jsonSchema = getExtractionJsonSchema();
|
|
80
|
+
const { stdout } = await execa("claude", [
|
|
81
|
+
"-p",
|
|
82
|
+
EXTRACTION_SYSTEM_PROMPT,
|
|
83
|
+
"--allowedTools",
|
|
84
|
+
"Read,Glob,Grep",
|
|
85
|
+
"--output-format",
|
|
86
|
+
"json",
|
|
87
|
+
"--json-schema",
|
|
88
|
+
jsonSchema
|
|
89
|
+
], {
|
|
90
|
+
input: transcript,
|
|
91
|
+
cwd: resolve(projectDir),
|
|
92
|
+
timeout: 3e5
|
|
93
|
+
});
|
|
94
|
+
const envelope = JSON.parse(stdout);
|
|
95
|
+
if (envelope.is_error) {
|
|
96
|
+
throw new Error(`claude -p failed: ${envelope.result || "unknown error"}`);
|
|
97
|
+
}
|
|
98
|
+
const data = envelope.structured_output ?? envelope.result;
|
|
99
|
+
if (!data) {
|
|
100
|
+
throw new Error("claude -p returned no structured output");
|
|
101
|
+
}
|
|
102
|
+
const parsed = typeof data === "string" ? JSON.parse(data) : data;
|
|
103
|
+
const result = ExtractionResultSchema.parse(parsed);
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/executor/worker.ts
|
|
108
|
+
import { execa as execa2 } from "execa";
|
|
109
|
+
import path from "path";
|
|
110
|
+
function generateSlug(title) {
|
|
111
|
+
return title.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/[\s-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50).replace(/-+$/, "");
|
|
112
|
+
}
|
|
113
|
+
async function createWorktree(projectDir, slug) {
|
|
114
|
+
const worktreePath = path.join(projectDir, ".worktrees", `pipit-${slug}`);
|
|
115
|
+
const branchName = `pipit/${slug}`;
|
|
116
|
+
await execa2("git", ["worktree", "add", worktreePath, "-b", branchName], {
|
|
117
|
+
cwd: projectDir
|
|
118
|
+
});
|
|
119
|
+
return worktreePath;
|
|
120
|
+
}
|
|
121
|
+
async function cleanupWorktree(projectDir, worktreePath) {
|
|
122
|
+
try {
|
|
123
|
+
await execa2("git", ["worktree", "remove", worktreePath, "--force"], {
|
|
124
|
+
cwd: projectDir
|
|
125
|
+
});
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function escapeAppleScript(str) {
|
|
130
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
131
|
+
}
|
|
132
|
+
function buildAppleScript(terminal, worktreePath, claudeCommand) {
|
|
133
|
+
switch (terminal) {
|
|
134
|
+
case "ghostty": {
|
|
135
|
+
const escapedPath = escapeAppleScript(worktreePath);
|
|
136
|
+
const escapedCmd = escapeAppleScript(claudeCommand);
|
|
137
|
+
return `
|
|
138
|
+
tell application "Ghostty"
|
|
139
|
+
activate
|
|
140
|
+
set cfg to new surface configuration
|
|
141
|
+
set initial working directory of cfg to "${escapedPath}"
|
|
142
|
+
set initial input of cfg to "${escapedCmd}\\n"
|
|
143
|
+
new tab in front window with configuration cfg
|
|
144
|
+
end tell
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
case "iterm2": {
|
|
148
|
+
const escapedCmd = escapeAppleScript(`cd '${worktreePath}' && ${claudeCommand}`);
|
|
149
|
+
return `
|
|
150
|
+
tell application "iTerm2"
|
|
151
|
+
tell current window
|
|
152
|
+
create tab with default profile
|
|
153
|
+
tell current session of current tab
|
|
154
|
+
write text "${escapedCmd}"
|
|
155
|
+
end tell
|
|
156
|
+
end tell
|
|
157
|
+
end tell
|
|
158
|
+
`;
|
|
159
|
+
}
|
|
160
|
+
case "warp":
|
|
161
|
+
case "apple-terminal":
|
|
162
|
+
default: {
|
|
163
|
+
const escapedCmd = escapeAppleScript(`cd '${worktreePath}' && ${claudeCommand}`);
|
|
164
|
+
return `
|
|
165
|
+
tell application "Terminal"
|
|
166
|
+
activate
|
|
167
|
+
do script "${escapedCmd}"
|
|
168
|
+
end tell
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function resolveTerminal() {
|
|
174
|
+
const override = process.env.PIPIT_TERMINAL?.toLowerCase();
|
|
175
|
+
if (override === "iterm2") return "iterm2";
|
|
176
|
+
if (override === "warp") return "warp";
|
|
177
|
+
if (override === "terminal") return "apple-terminal";
|
|
178
|
+
return "ghostty";
|
|
179
|
+
}
|
|
180
|
+
async function launchClaudeTab(workingDir, claudeCommand) {
|
|
181
|
+
const terminal = resolveTerminal();
|
|
182
|
+
const script = buildAppleScript(terminal, workingDir, claudeCommand);
|
|
183
|
+
await execa2("osascript", ["-e", script]);
|
|
184
|
+
}
|
|
185
|
+
async function implementTask(config) {
|
|
186
|
+
const { task, projectDir, transcriptContext } = config;
|
|
187
|
+
const slug = generateSlug(task.title);
|
|
188
|
+
const branchName = `pipit/${slug}`;
|
|
189
|
+
let worktreePath = "";
|
|
190
|
+
try {
|
|
191
|
+
worktreePath = await createWorktree(projectDir, slug);
|
|
192
|
+
const prompt = [
|
|
193
|
+
`## Task: ${task.title}`,
|
|
194
|
+
"",
|
|
195
|
+
`### Description`,
|
|
196
|
+
task.description,
|
|
197
|
+
"",
|
|
198
|
+
`### Transcript Context`,
|
|
199
|
+
transcriptContext,
|
|
200
|
+
"",
|
|
201
|
+
`### Instructions`,
|
|
202
|
+
`Implement the task described above. Follow existing code conventions.`,
|
|
203
|
+
`If relevant files were identified, start there: ${task.relevant_files.join(", ") || "none specified"}.`,
|
|
204
|
+
`When done, commit your changes with a descriptive message.`
|
|
205
|
+
].join("\n");
|
|
206
|
+
const escapedPrompt = prompt.replace(/'/g, "'\\''");
|
|
207
|
+
const claudeCommand = `claude --dangerously-skip-permissions '${escapedPrompt}'`;
|
|
208
|
+
await launchClaudeTab(worktreePath, claudeCommand);
|
|
209
|
+
return {
|
|
210
|
+
success: true,
|
|
211
|
+
branch_name: branchName,
|
|
212
|
+
files_changed: [],
|
|
213
|
+
worktree_path: worktreePath
|
|
214
|
+
};
|
|
215
|
+
} catch (err) {
|
|
216
|
+
if (worktreePath) {
|
|
217
|
+
await cleanupWorktree(projectDir, worktreePath);
|
|
218
|
+
}
|
|
219
|
+
const message = err instanceof Error ? err.message : "Unknown error launching implementation session";
|
|
220
|
+
return {
|
|
221
|
+
success: false,
|
|
222
|
+
branch_name: branchName,
|
|
223
|
+
files_changed: [],
|
|
224
|
+
worktree_path: worktreePath,
|
|
225
|
+
error: message
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/input/resolver.ts
|
|
231
|
+
import { access } from "fs/promises";
|
|
232
|
+
import { resolve as resolve2, extname } from "path";
|
|
233
|
+
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
|
|
234
|
+
function inferInputType(raw) {
|
|
235
|
+
if (raw === "-") return "stdin";
|
|
236
|
+
if (raw.startsWith("http://") || raw.startsWith("https://")) return "url";
|
|
237
|
+
if (IMAGE_EXTENSIONS.has(extname(raw).toLowerCase())) return "image";
|
|
238
|
+
return "file";
|
|
239
|
+
}
|
|
240
|
+
async function resolveInput(raw, typeOverride) {
|
|
241
|
+
const type = typeOverride ?? inferInputType(raw);
|
|
242
|
+
switch (type) {
|
|
243
|
+
case "file":
|
|
244
|
+
case "image": {
|
|
245
|
+
const absolutePath = resolve2(raw);
|
|
246
|
+
await access(absolutePath);
|
|
247
|
+
return { type, path: absolutePath, raw };
|
|
248
|
+
}
|
|
249
|
+
case "url": {
|
|
250
|
+
return { type, url: raw, raw };
|
|
251
|
+
}
|
|
252
|
+
case "stdin": {
|
|
253
|
+
const content = await readStdin();
|
|
254
|
+
if (!content.trim()) {
|
|
255
|
+
throw new InputError("No input received on stdin", 2);
|
|
256
|
+
}
|
|
257
|
+
return { type, content, raw };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
async function readStdin() {
|
|
262
|
+
if (process.stdin.isTTY) {
|
|
263
|
+
throw new InputError("No input on stdin (not piped)", 2);
|
|
264
|
+
}
|
|
265
|
+
process.stderr.write("Reading from stdin...\n");
|
|
266
|
+
const chunks = [];
|
|
267
|
+
for await (const chunk of process.stdin) {
|
|
268
|
+
chunks.push(chunk);
|
|
269
|
+
}
|
|
270
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
271
|
+
}
|
|
272
|
+
var InputError = class extends Error {
|
|
273
|
+
constructor(message, exitCode) {
|
|
274
|
+
super(message);
|
|
275
|
+
this.exitCode = exitCode;
|
|
276
|
+
this.name = "InputError";
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// src/input/handlers/text-file.ts
|
|
281
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
282
|
+
async function handleTextFile(input) {
|
|
283
|
+
const content = await readFile2(input.path, "utf-8");
|
|
284
|
+
const text = [
|
|
285
|
+
"<transcript>",
|
|
286
|
+
content,
|
|
287
|
+
"</transcript>"
|
|
288
|
+
].join("\n");
|
|
289
|
+
return { text, claudeFlags: [] };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/input/handlers/image-file.ts
|
|
293
|
+
async function handleImageFile(input) {
|
|
294
|
+
const absolutePath = input.path;
|
|
295
|
+
const text = `[Image attached: ${absolutePath}]`;
|
|
296
|
+
return {
|
|
297
|
+
text,
|
|
298
|
+
claudeFlags: ["--image", absolutePath]
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/input/handlers/url.ts
|
|
303
|
+
var MAX_CONTENT_LENGTH = 5e4;
|
|
304
|
+
var FETCH_TIMEOUT_MS = 1e4;
|
|
305
|
+
async function handleUrl(input) {
|
|
306
|
+
const url = input.url;
|
|
307
|
+
const controller = new AbortController();
|
|
308
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
309
|
+
let response;
|
|
310
|
+
try {
|
|
311
|
+
response = await fetch(url, {
|
|
312
|
+
signal: controller.signal,
|
|
313
|
+
headers: {
|
|
314
|
+
Accept: "text/html, text/plain, */*"
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
} catch (err) {
|
|
318
|
+
clearTimeout(timeout);
|
|
319
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
320
|
+
throw new InputError(`URL fetch timed out after 10s: ${url}`, 2);
|
|
321
|
+
}
|
|
322
|
+
throw new InputError(`Failed to fetch URL: ${url}`, 2);
|
|
323
|
+
} finally {
|
|
324
|
+
clearTimeout(timeout);
|
|
325
|
+
}
|
|
326
|
+
if (!response.ok) {
|
|
327
|
+
throw new InputError(`URL returned ${response.status}: ${url}`, 2);
|
|
328
|
+
}
|
|
329
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
330
|
+
if (!contentType.includes("text/") && !contentType.includes("application/json")) {
|
|
331
|
+
throw new InputError(`Unsupported content type: ${contentType}`, 2);
|
|
332
|
+
}
|
|
333
|
+
const rawContent = await response.text();
|
|
334
|
+
let content = rawContent.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
335
|
+
let truncated = false;
|
|
336
|
+
if (content.length > MAX_CONTENT_LENGTH) {
|
|
337
|
+
content = content.slice(0, MAX_CONTENT_LENGTH);
|
|
338
|
+
truncated = true;
|
|
339
|
+
}
|
|
340
|
+
const parts = [
|
|
341
|
+
`<article source="${url}">`,
|
|
342
|
+
content,
|
|
343
|
+
...truncated ? [`[Content truncated at ${MAX_CONTENT_LENGTH.toLocaleString()} characters]`] : [],
|
|
344
|
+
"</article>"
|
|
345
|
+
];
|
|
346
|
+
return { text: parts.join("\n"), claudeFlags: [] };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// src/input/handlers/stdin.ts
|
|
350
|
+
async function handleStdin(input) {
|
|
351
|
+
const text = [
|
|
352
|
+
"<transcript>",
|
|
353
|
+
input.content,
|
|
354
|
+
"</transcript>"
|
|
355
|
+
].join("\n");
|
|
356
|
+
return { text, claudeFlags: [] };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/input/prompt-builder.ts
|
|
360
|
+
async function buildPromptContent(input, context) {
|
|
361
|
+
let result;
|
|
362
|
+
switch (input.type) {
|
|
363
|
+
case "file":
|
|
364
|
+
result = await handleTextFile(input);
|
|
365
|
+
break;
|
|
366
|
+
case "image":
|
|
367
|
+
result = await handleImageFile(input);
|
|
368
|
+
break;
|
|
369
|
+
case "url":
|
|
370
|
+
result = await handleUrl(input);
|
|
371
|
+
break;
|
|
372
|
+
case "stdin":
|
|
373
|
+
result = await handleStdin(input);
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
if (context) {
|
|
377
|
+
result = {
|
|
378
|
+
text: result.text + "\n\nAdditional instructions from the user:\n" + context,
|
|
379
|
+
claudeFlags: [...result.claudeFlags]
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/prompts/shared.ts
|
|
386
|
+
function formatProjectContext(name, dirs) {
|
|
387
|
+
const dirList = dirs.map((d) => ` - ${d}`).join("\n");
|
|
388
|
+
return [
|
|
389
|
+
`<project>`,
|
|
390
|
+
`Name: ${name}`,
|
|
391
|
+
`Directories:`,
|
|
392
|
+
dirList,
|
|
393
|
+
`</project>`
|
|
394
|
+
].join("\n");
|
|
395
|
+
}
|
|
396
|
+
function formatInputSection(content, type) {
|
|
397
|
+
if (!content || content.trim() === "") {
|
|
398
|
+
return [
|
|
399
|
+
`<context type="empty">`,
|
|
400
|
+
`The user wants to start a session for this project. Ask what they'd like to work on.`,
|
|
401
|
+
`</context>`
|
|
402
|
+
].join("\n");
|
|
403
|
+
}
|
|
404
|
+
if (type === "image") {
|
|
405
|
+
return [
|
|
406
|
+
`<context type="image">`,
|
|
407
|
+
content,
|
|
408
|
+
`Analyze the attached image for tasks, bugs, or design changes.`,
|
|
409
|
+
`</context>`
|
|
410
|
+
].join("\n");
|
|
411
|
+
}
|
|
412
|
+
return [
|
|
413
|
+
`<context type="${type}">`,
|
|
414
|
+
content,
|
|
415
|
+
`</context>`
|
|
416
|
+
].join("\n");
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/prompts/fire-and-forget.ts
|
|
420
|
+
function buildFireAndForgetPrompt(config) {
|
|
421
|
+
const { projectName, projectDirs, inputContent, inputType, context, joycraft } = config;
|
|
422
|
+
const projectBlock = formatProjectContext(projectName, projectDirs);
|
|
423
|
+
const inputBlock = formatInputSection(inputContent, inputType);
|
|
424
|
+
const joycraftInstructions = joycraft ? [
|
|
425
|
+
`Joycraft is available in this project. Use it to:`,
|
|
426
|
+
` 1. Write atomic specs for each task you identify`,
|
|
427
|
+
` 2. Implement each spec`,
|
|
428
|
+
` 3. Open a PR with all changes`
|
|
429
|
+
].join("\n") : [
|
|
430
|
+
`Make a plan for implementation, prioritize the tasks, implement them,`,
|
|
431
|
+
`and commit your work. Open a PR when done.`
|
|
432
|
+
].join("\n");
|
|
433
|
+
const contextAddition = context ? [
|
|
434
|
+
``,
|
|
435
|
+
`<additional-context>`,
|
|
436
|
+
`Additional instructions from the user:`,
|
|
437
|
+
context,
|
|
438
|
+
`</additional-context>`
|
|
439
|
+
].join("\n") : "";
|
|
440
|
+
return [
|
|
441
|
+
projectBlock,
|
|
442
|
+
``,
|
|
443
|
+
inputBlock,
|
|
444
|
+
contextAddition,
|
|
445
|
+
``,
|
|
446
|
+
`<instructions>`,
|
|
447
|
+
`You are working on the "${projectName}" project. Analyze the context provided above`,
|
|
448
|
+
`and extract all relevant development tasks and action items.`,
|
|
449
|
+
``,
|
|
450
|
+
joycraftInstructions,
|
|
451
|
+
``,
|
|
452
|
+
`Work fully autonomously -- do not wait for user input. You have full authority`,
|
|
453
|
+
`to analyze, plan, implement, and open a PR.`,
|
|
454
|
+
``,
|
|
455
|
+
`If critical information is missing, proceed with reasonable assumptions and note`,
|
|
456
|
+
`them in the PR description. Do not block on unanswered questions.`,
|
|
457
|
+
``,
|
|
458
|
+
`Spin up teammates to execute tasks in parallel where possible.`,
|
|
459
|
+
`</instructions>`
|
|
460
|
+
].join("\n");
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/prompts/interactive.ts
|
|
464
|
+
function buildInteractivePrompt(config) {
|
|
465
|
+
const { projectName, projectDirs, inputContent, inputType, context, joycraft } = config;
|
|
466
|
+
const projectBlock = formatProjectContext(projectName, projectDirs);
|
|
467
|
+
const inputBlock = formatInputSection(inputContent, inputType);
|
|
468
|
+
const joycraftInstructions = joycraft ? [
|
|
469
|
+
`Joycraft is available in this project. Start by running /interview or /new-feature`,
|
|
470
|
+
`with the context below as starting material. Let the interview process guide`,
|
|
471
|
+
`the conversation before any implementation begins.`
|
|
472
|
+
].join("\n") : [
|
|
473
|
+
`Start by having a conversation about the context above. Ask clarifying questions`,
|
|
474
|
+
`to understand the user's goals and priorities before implementing anything.`,
|
|
475
|
+
`Do not write code until the user confirms the plan.`
|
|
476
|
+
].join("\n");
|
|
477
|
+
const contextAddition = context ? [
|
|
478
|
+
``,
|
|
479
|
+
`<additional-context>`,
|
|
480
|
+
`Additional instructions from the user:`,
|
|
481
|
+
context,
|
|
482
|
+
`</additional-context>`
|
|
483
|
+
].join("\n") : "";
|
|
484
|
+
return [
|
|
485
|
+
projectBlock,
|
|
486
|
+
``,
|
|
487
|
+
inputBlock,
|
|
488
|
+
contextAddition,
|
|
489
|
+
``,
|
|
490
|
+
`<instructions>`,
|
|
491
|
+
`You are working on the "${projectName}" project. The user has pipit'd the context`,
|
|
492
|
+
`above into this session for discussion and collaboration.`,
|
|
493
|
+
``,
|
|
494
|
+
joycraftInstructions,
|
|
495
|
+
``,
|
|
496
|
+
`Wait for the user to participate before implementing anything. This is a`,
|
|
497
|
+
`collaborative session -- explore the context together, clarify requirements,`,
|
|
498
|
+
`and agree on an approach before writing any code.`,
|
|
499
|
+
`</instructions>`
|
|
500
|
+
].join("\n");
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// src/joycraft/detector.ts
|
|
504
|
+
import { existsSync } from "fs";
|
|
505
|
+
import { join } from "path";
|
|
506
|
+
function detectJoycraft(projectDir) {
|
|
507
|
+
return existsSync(join(projectDir, ".claude", "skills"));
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// src/input/types.ts
|
|
511
|
+
import { z as z2 } from "zod";
|
|
512
|
+
var InputTypeSchema = z2.enum(["file", "image", "url", "stdin"]);
|
|
513
|
+
var ControlLevelSchema = z2.enum(["auto", "plan_first", "extract_only"]);
|
|
514
|
+
var SessionModeSchema = z2.enum(["fire_and_forget", "interactive"]);
|
|
515
|
+
var JsonOutputSchema = z2.object({
|
|
516
|
+
success: z2.boolean(),
|
|
517
|
+
input_type: InputTypeSchema.optional(),
|
|
518
|
+
input_resolved: z2.string().optional(),
|
|
519
|
+
project_dir: z2.string().optional(),
|
|
520
|
+
terminal: z2.string().optional(),
|
|
521
|
+
prompt_version: z2.number().optional(),
|
|
522
|
+
error_code: z2.number().optional(),
|
|
523
|
+
error: z2.string().optional()
|
|
524
|
+
});
|
|
525
|
+
var EXIT = {
|
|
526
|
+
SUCCESS: 0,
|
|
527
|
+
GENERAL_ERROR: 1,
|
|
528
|
+
INPUT_NOT_FOUND: 2,
|
|
529
|
+
INVALID_ARGS: 3,
|
|
530
|
+
TERMINAL_LAUNCH_FAILED: 4
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
// src/cli.ts
|
|
534
|
+
var program = new Command();
|
|
535
|
+
program.name("pipit").description(
|
|
536
|
+
"Pipe any context into Claude Code sessions"
|
|
537
|
+
).version("0.1.0");
|
|
538
|
+
function collect(value, previous) {
|
|
539
|
+
return previous.concat([value]);
|
|
540
|
+
}
|
|
541
|
+
program.command("process").description("Process a meeting transcript file").argument("<transcript-file>", "Path to the transcript file to process").option("-p, --project-dir <dir>", "Target project directory").option("-n, --dry-run", "Extract tasks without implementing them").option(
|
|
542
|
+
"-t, --task-index <index>",
|
|
543
|
+
"Only implement a specific task by index",
|
|
544
|
+
parseInt
|
|
545
|
+
).action(
|
|
546
|
+
async (transcriptFile, options) => {
|
|
547
|
+
try {
|
|
548
|
+
const absoluteTranscriptPath = resolve3(transcriptFile);
|
|
549
|
+
try {
|
|
550
|
+
await access2(absoluteTranscriptPath);
|
|
551
|
+
} catch {
|
|
552
|
+
console.error(`Error: Transcript file not found: ${absoluteTranscriptPath}`);
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
const projectDir = resolve3(options.projectDir ?? process.cwd());
|
|
556
|
+
console.log(`Extracting tasks from ${absoluteTranscriptPath}...`);
|
|
557
|
+
const result = await extractTasks(absoluteTranscriptPath, projectDir);
|
|
558
|
+
console.log("");
|
|
559
|
+
console.log(`Meeting summary: ${result.meeting_summary}`);
|
|
560
|
+
console.log("");
|
|
561
|
+
console.log(`Action items found: ${result.action_items.length}`);
|
|
562
|
+
for (let i = 0; i < result.action_items.length; i++) {
|
|
563
|
+
const item = result.action_items[i];
|
|
564
|
+
console.log(
|
|
565
|
+
` [${i}] [${item.priority}] [${item.estimated_complexity}] ${item.title}`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
if (result.decisions.length > 0) {
|
|
569
|
+
console.log("");
|
|
570
|
+
console.log("Decisions made:");
|
|
571
|
+
for (const decision of result.decisions) {
|
|
572
|
+
console.log(` - ${decision}`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (result.questions_to_clarify.length > 0) {
|
|
576
|
+
console.log("");
|
|
577
|
+
console.log("Questions to clarify:");
|
|
578
|
+
for (const question of result.questions_to_clarify) {
|
|
579
|
+
console.log(` - ${question}`);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (options.dryRun) {
|
|
583
|
+
console.log("");
|
|
584
|
+
console.log("Dry run -- skipping implementation");
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
const taskIndex = options.taskIndex ?? 0;
|
|
588
|
+
if (taskIndex < 0 || taskIndex >= result.action_items.length) {
|
|
589
|
+
console.error(
|
|
590
|
+
`Error: Task index ${taskIndex} is out of range (0-${result.action_items.length - 1})`
|
|
591
|
+
);
|
|
592
|
+
process.exit(1);
|
|
593
|
+
}
|
|
594
|
+
const task = result.action_items[taskIndex];
|
|
595
|
+
console.log("");
|
|
596
|
+
console.log(`Implementing task: ${task.title}...`);
|
|
597
|
+
const taskResult = await implementTask({
|
|
598
|
+
task,
|
|
599
|
+
projectDir,
|
|
600
|
+
transcriptContext: task.transcript_context
|
|
601
|
+
});
|
|
602
|
+
console.log("");
|
|
603
|
+
if (taskResult.success) {
|
|
604
|
+
console.log("Claude Code session launched in a new terminal tab.");
|
|
605
|
+
console.log(` Branch: ${taskResult.branch_name}`);
|
|
606
|
+
console.log(` Worktree: ${taskResult.worktree_path}`);
|
|
607
|
+
console.log("");
|
|
608
|
+
console.log("When Claude finishes, you can:");
|
|
609
|
+
console.log(` cd ${taskResult.worktree_path}`);
|
|
610
|
+
console.log(` git push origin ${taskResult.branch_name} && gh pr create`);
|
|
611
|
+
} else {
|
|
612
|
+
console.error(`Failed to launch session: ${taskResult.error}`);
|
|
613
|
+
process.exit(1);
|
|
614
|
+
}
|
|
615
|
+
} catch (err) {
|
|
616
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
617
|
+
console.error(`Error: ${message}`);
|
|
618
|
+
process.exit(1);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
);
|
|
622
|
+
program.command("go").description("Send any context to Claude Code -- let it extract, plan, and implement").argument("<input>", "Input: file path, URL (http/https), or - for stdin").argument("<project-name>", "Name of the project this context is for").option("-p, --project-dir <dir>", "Project directory (repeatable for multi-dir projects)", collect, []).addOption(
|
|
623
|
+
new Option("--input-type <type>", "Override input type inference").choices(["file", "image", "url", "stdin"])
|
|
624
|
+
).addOption(
|
|
625
|
+
new Option("--control-level <level>", "Control level for Claude session").choices(["auto", "plan_first", "extract_only"]).default("auto")
|
|
626
|
+
).option("--terminal <name>", "Terminal to use (ghostty, iterm2, warp, terminal)").option("--context <text>", "Additional instructions appended to the prompt").option("--dry-run", "Print the resolved prompt without launching", false).option("--json", "Emit machine-readable JSON to stdout", false).addOption(
|
|
627
|
+
new Option("--mode <mode>", "Session mode").choices(["fire_and_forget", "interactive"]).default("fire_and_forget")
|
|
628
|
+
).action(
|
|
629
|
+
async (rawInput, projectName, opts) => {
|
|
630
|
+
const inputType = opts.inputType ? InputTypeSchema.parse(opts.inputType) : void 0;
|
|
631
|
+
const controlLevel = ControlLevelSchema.parse(opts.controlLevel);
|
|
632
|
+
const mode = SessionModeSchema.parse(opts.mode);
|
|
633
|
+
const projectDirs = opts.projectDir.length > 0 ? opts.projectDir.map((d) => resolve3(d)) : [resolve3(process.cwd())];
|
|
634
|
+
const options = {
|
|
635
|
+
projectDir: projectDirs,
|
|
636
|
+
inputType,
|
|
637
|
+
controlLevel,
|
|
638
|
+
terminal: opts.terminal,
|
|
639
|
+
context: opts.context,
|
|
640
|
+
dryRun: opts.dryRun,
|
|
641
|
+
json: opts.json,
|
|
642
|
+
mode
|
|
643
|
+
};
|
|
644
|
+
function outputJson(data) {
|
|
645
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
646
|
+
}
|
|
647
|
+
function fail(message, exitCode) {
|
|
648
|
+
if (options.json) {
|
|
649
|
+
outputJson({ success: false, error_code: exitCode, error: message });
|
|
650
|
+
} else {
|
|
651
|
+
console.error(`Error: ${message}`);
|
|
652
|
+
}
|
|
653
|
+
process.exit(exitCode);
|
|
654
|
+
}
|
|
655
|
+
try {
|
|
656
|
+
const resolved = await resolveInput(rawInput, inputType);
|
|
657
|
+
const promptContent = await buildPromptContent(resolved, options.context);
|
|
658
|
+
const joycraft = detectJoycraft(projectDirs[0]);
|
|
659
|
+
const promptConfig = {
|
|
660
|
+
projectName,
|
|
661
|
+
projectDirs,
|
|
662
|
+
inputContent: promptContent.text,
|
|
663
|
+
inputType: resolved.type,
|
|
664
|
+
context: options.context,
|
|
665
|
+
mode,
|
|
666
|
+
joycraft
|
|
667
|
+
};
|
|
668
|
+
const prompt = mode === "interactive" ? buildInteractivePrompt(promptConfig) : buildFireAndForgetPrompt(promptConfig);
|
|
669
|
+
if (options.dryRun) {
|
|
670
|
+
if (options.json) {
|
|
671
|
+
outputJson({
|
|
672
|
+
success: true,
|
|
673
|
+
input_type: resolved.type,
|
|
674
|
+
input_resolved: resolved.path ?? resolved.url ?? "(stdin)",
|
|
675
|
+
project_dir: projectDirs[0],
|
|
676
|
+
terminal: options.terminal ?? process.env.PIPIT_TERMINAL ?? "ghostty",
|
|
677
|
+
prompt_version: 2
|
|
678
|
+
});
|
|
679
|
+
} else {
|
|
680
|
+
console.log(prompt);
|
|
681
|
+
}
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
const escapedPrompt = prompt.replace(/'/g, "'\\''");
|
|
685
|
+
const controlFlag = controlLevel === "auto" ? "--dangerously-skip-permissions" : "";
|
|
686
|
+
const extraFlags = promptContent.claudeFlags.map((f) => f.includes(" ") ? `'${f.replace(/'/g, "'\\''")}'` : f).join(" ");
|
|
687
|
+
const claudeCommand = `claude ${controlFlag} ${extraFlags} '${escapedPrompt}'`.replace(/\s+/g, " ").trim();
|
|
688
|
+
if (options.terminal) {
|
|
689
|
+
process.env.PIPIT_TERMINAL = options.terminal;
|
|
690
|
+
}
|
|
691
|
+
const workingDir = projectDirs[0];
|
|
692
|
+
if (!options.json) {
|
|
693
|
+
console.log(`Launching Claude Code session for ${projectName}...`);
|
|
694
|
+
}
|
|
695
|
+
try {
|
|
696
|
+
await launchClaudeTab(workingDir, claudeCommand);
|
|
697
|
+
} catch (err) {
|
|
698
|
+
const msg = err instanceof Error ? err.message : "Terminal launch failed";
|
|
699
|
+
fail(msg, EXIT.TERMINAL_LAUNCH_FAILED);
|
|
700
|
+
}
|
|
701
|
+
if (options.json) {
|
|
702
|
+
outputJson({
|
|
703
|
+
success: true,
|
|
704
|
+
input_type: resolved.type,
|
|
705
|
+
input_resolved: resolved.path ?? resolved.url ?? "(stdin)",
|
|
706
|
+
project_dir: workingDir,
|
|
707
|
+
terminal: options.terminal ?? process.env.PIPIT_TERMINAL ?? "ghostty",
|
|
708
|
+
prompt_version: 2
|
|
709
|
+
});
|
|
710
|
+
} else {
|
|
711
|
+
console.log("");
|
|
712
|
+
console.log("Claude Code session launched in a new terminal tab.");
|
|
713
|
+
console.log(` Project: ${workingDir}`);
|
|
714
|
+
if (projectDirs.length > 1) {
|
|
715
|
+
console.log(` Additional dirs: ${projectDirs.slice(1).join(", ")}`);
|
|
716
|
+
}
|
|
717
|
+
console.log("");
|
|
718
|
+
console.log("Claude will extract tasks, plan, and implement. Watch the tab!");
|
|
719
|
+
}
|
|
720
|
+
} catch (err) {
|
|
721
|
+
if (err instanceof InputError) {
|
|
722
|
+
fail(err.message, err.exitCode);
|
|
723
|
+
}
|
|
724
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
725
|
+
fail(message, EXIT.GENERAL_ERROR);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
);
|
|
729
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pipit-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Pipe any context into Claude Code sessions. Screenshots, meeting notes, URLs, clipboard -- capture it and let Claude handle the rest.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"pipit": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsup",
|
|
14
|
+
"dev": "tsup --watch",
|
|
15
|
+
"test": "vitest",
|
|
16
|
+
"lint": "eslint .",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"prepublishOnly": "pnpm build && pnpm test --run && pnpm typecheck"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"claude",
|
|
22
|
+
"claude-code",
|
|
23
|
+
"ai",
|
|
24
|
+
"cli",
|
|
25
|
+
"transcript",
|
|
26
|
+
"meeting-notes",
|
|
27
|
+
"automation"
|
|
28
|
+
],
|
|
29
|
+
"author": "Maximilian Maksutovic",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/maksutovic/pipit.git"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/maksutovic/pipit#readme",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"commander": "^13.1.0",
|
|
38
|
+
"execa": "^9.5.2",
|
|
39
|
+
"zod": "^3.24.2"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.13.10",
|
|
43
|
+
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
|
44
|
+
"@typescript-eslint/parser": "^8.26.1",
|
|
45
|
+
"eslint": "^9.22.0",
|
|
46
|
+
"tsup": "^8.4.0",
|
|
47
|
+
"typescript": "^5.8.2",
|
|
48
|
+
"vitest": "^3.0.9"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20"
|
|
52
|
+
},
|
|
53
|
+
"packageManager": "pnpm@10.19.0"
|
|
54
|
+
}
|