agentpack-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 +174 -0
- package/SECURITY.md +44 -0
- package/assets/agentpack-logo.jpg +0 -0
- package/dist/src/agentpack.d.ts +2 -0
- package/dist/src/agentpack.js +8 -0
- package/dist/src/agentpack.js.map +1 -0
- package/dist/src/cli/index.d.ts +3 -0
- package/dist/src/cli/index.js +412 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/core/budget.d.ts +14 -0
- package/dist/src/core/budget.js +56 -0
- package/dist/src/core/budget.js.map +1 -0
- package/dist/src/core/checkpoints.d.ts +24 -0
- package/dist/src/core/checkpoints.js +93 -0
- package/dist/src/core/checkpoints.js.map +1 -0
- package/dist/src/core/doctor.d.ts +4 -0
- package/dist/src/core/doctor.js +304 -0
- package/dist/src/core/doctor.js.map +1 -0
- package/dist/src/core/git.d.ts +2 -0
- package/dist/src/core/git.js +34 -0
- package/dist/src/core/git.js.map +1 -0
- package/dist/src/core/hash.d.ts +5 -0
- package/dist/src/core/hash.js +29 -0
- package/dist/src/core/hash.js.map +1 -0
- package/dist/src/core/ids.d.ts +1 -0
- package/dist/src/core/ids.js +7 -0
- package/dist/src/core/ids.js.map +1 -0
- package/dist/src/core/presets.d.ts +12 -0
- package/dist/src/core/presets.js +24 -0
- package/dist/src/core/presets.js.map +1 -0
- package/dist/src/core/redaction.d.ts +4 -0
- package/dist/src/core/redaction.js +18 -0
- package/dist/src/core/redaction.js.map +1 -0
- package/dist/src/core/resume.d.ts +13 -0
- package/dist/src/core/resume.js +398 -0
- package/dist/src/core/resume.js.map +1 -0
- package/dist/src/core/store.d.ts +20 -0
- package/dist/src/core/store.js +240 -0
- package/dist/src/core/store.js.map +1 -0
- package/dist/src/core/types.d.ts +43 -0
- package/dist/src/core/types.js +2 -0
- package/dist/src/core/types.js.map +1 -0
- package/dist/src/integrations/install.d.ts +5 -0
- package/dist/src/integrations/install.js +361 -0
- package/dist/src/integrations/install.js.map +1 -0
- package/dist/src/mcp/server.d.ts +9 -0
- package/dist/src/mcp/server.js +347 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/operations.d.ts +29 -0
- package/dist/src/operations.js +199 -0
- package/dist/src/operations.js.map +1 -0
- package/docs/DOGFOOD.md +72 -0
- package/docs/INTEGRATIONS.md +183 -0
- package/docs/MCP.md +83 -0
- package/docs/MVP.md +97 -0
- package/docs/ROADMAP.md +129 -0
- package/docs/SETUP.md +56 -0
- package/docs/agentpack-flow.md +63 -0
- package/package.json +55 -0
- package/scripts/mcp-smoke.mjs +197 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Agentpack contributors
|
|
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,174 @@
|
|
|
1
|
+
# Agentpack
|
|
2
|
+
|
|
3
|
+
Local task-state ledger for AI coding agents.
|
|
4
|
+
|
|
5
|
+
> Coding agents forget. Agentpack gives them the task state they need to continue.
|
|
6
|
+
|
|
7
|
+
Agentpack helps coding agents continue long-running repo work without rediscovering context, re-reading unchanged sources, or repeating dead ends.
|
|
8
|
+
|
|
9
|
+
## Product Contract
|
|
10
|
+
|
|
11
|
+
Agentpack is a local-first open-source tool for repo-scoped coding work. It is not a general AI memory, a knowledge graph, or a chat archive. Instead, it keeps a compact task ledger in `.agentpack/` and exposes that state through simple surfaces:
|
|
12
|
+
|
|
13
|
+
- files in `.agentpack/`
|
|
14
|
+
- CLI commands
|
|
15
|
+
- a local MCP server
|
|
16
|
+
- project instructions such as `AGENTS.md`, `CLAUDE.md`, and Cursor rules
|
|
17
|
+
|
|
18
|
+
`.agentpack/` is local task state and is ignored by git by default. Agentpack is designed first for coding agents such as Codex, Claude Code, Cursor, and other MCP clients. Markdown export exists as a fallback for manual handoff, not as the primary workflow.
|
|
19
|
+
|
|
20
|
+
## v0 Scope
|
|
21
|
+
|
|
22
|
+
The first version is intentionally small:
|
|
23
|
+
|
|
24
|
+
- initialize a local `.agentpack/`
|
|
25
|
+
- record decisions, dead ends, evidence, and inspected sources
|
|
26
|
+
- store file hashes so agents can avoid re-reading unchanged files
|
|
27
|
+
- check whether recorded sources are unchanged, changed, or missing
|
|
28
|
+
- create checkpoints with git status, git diff, and generated resume context
|
|
29
|
+
- run a minimal local MCP server for coding-agent clients
|
|
30
|
+
- export a budgeted markdown handoff for manual fallback workflows
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install -g agentpack-cli
|
|
36
|
+
agentpack init
|
|
37
|
+
agentpack install codex --write
|
|
38
|
+
# or: agentpack install claude --write
|
|
39
|
+
# or: agentpack install cursor --write
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Restart or reconnect the coding-agent client. The generated project instructions tell the agent to load Agentpack context at the start, record durable decisions/sources/evidence while working, and checkpoint meaningful progress.
|
|
43
|
+
|
|
44
|
+
Use `agentpack doctor` to verify the local setup. Use `agentpack resume --preset agent --query "<topic>"` when you want to inspect the task state yourself.
|
|
45
|
+
|
|
46
|
+
For local development in this repo:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
fnm use 22
|
|
50
|
+
npm ci --ignore-scripts
|
|
51
|
+
npm test
|
|
52
|
+
npm run mcp:smoke
|
|
53
|
+
node dist/src/agentpack.js --help
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If `npm` is not available yet, install Node through `fnm` first:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
brew install fnm
|
|
60
|
+
fnm install 22
|
|
61
|
+
fnm default 22
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Then add this to `~/.zshrc` and restart the terminal:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
eval "$(fnm env --use-on-cd --shell zsh)"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
See [docs/SETUP.md](docs/SETUP.md) for the full setup guide.
|
|
71
|
+
|
|
72
|
+
This repo uses Agentpack itself through MCP. See [docs/DOGFOOD.md](docs/DOGFOOD.md) for the working protocol.
|
|
73
|
+
|
|
74
|
+
To verify the local MCP server without configuring an agent client yet:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm run mcp:smoke
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The smoke runner creates a temporary Agentpack workspace, starts `agentpack mcp`, sends `initialize`, `tools/list`, and a short `resume` flow, then deletes the temporary workspace.
|
|
81
|
+
|
|
82
|
+
See [docs/INTEGRATIONS.md](docs/INTEGRATIONS.md) for safe Codex, Claude Code, and Cursor setup.
|
|
83
|
+
See [docs/agentpack-flow.md](docs/agentpack-flow.md) for a visual execution flow.
|
|
84
|
+
|
|
85
|
+
## Coding-Agent Loop
|
|
86
|
+
|
|
87
|
+
Agentpack's core loop is built for coding agents working in the same repo over long sessions, compaction, restarts, or handoffs:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
agentpack resume --preset agent --query "MCP install"
|
|
91
|
+
agentpack source status
|
|
92
|
+
agentpack checkpoint -m "MCP install tested" --status "Ready for docs polish" --next "Update integration docs"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`resume --preset agent` gives the next coding agent the current goal, status, next actions, git state, durable decisions, dead ends, evidence, and source-cache guidance under a rough context budget. `source status` tells the agent which recorded source conclusions are still valid and which files need to be reopened. It compares the current file content to the hash recorded with the source conclusion; it is not a replacement for `git status`. Each recorded source shows hash status and git status separately, and git changes that were never recorded as sources are listed separately.
|
|
96
|
+
|
|
97
|
+
For manual web-chat fallback, `export --to markdown --preset chat` writes a handoff file under `.agentpack/exports/`. For the normal coding-agent workflow, `resume --preset agent` prints a larger task state directly in the terminal or MCP response. Add `--query` when you want Source Cache to include full summaries for sources relevant to the next task, always include changed/missing source records in full, and keep compact path/status/topic stubs for the rest.
|
|
98
|
+
|
|
99
|
+
In a new session, start by loading Agentpack MCP context, or by pasting a markdown handoff when MCP is not available. Then inspect only the files marked changed or missing. During work, record durable decisions, failed approaches, evidence, and source conclusions. End a coherent step with a checkpoint so the next agent inherits a compact state instead of a pile of chat history.
|
|
100
|
+
|
|
101
|
+
## Security Posture
|
|
102
|
+
|
|
103
|
+
Agentpack keeps the v0 supply chain deliberately small:
|
|
104
|
+
|
|
105
|
+
- zero runtime dependencies
|
|
106
|
+
- exact dev dependency versions
|
|
107
|
+
- committed `package-lock.json`
|
|
108
|
+
- `ignore-scripts=true`
|
|
109
|
+
- no telemetry
|
|
110
|
+
- no network calls during normal CLI or MCP operation
|
|
111
|
+
- best-effort redaction for common secret-looking values in stored context and handoff outputs
|
|
112
|
+
- npm provenance prepared for future public releases
|
|
113
|
+
|
|
114
|
+
## Core Idea
|
|
115
|
+
|
|
116
|
+
Agentpack stores:
|
|
117
|
+
|
|
118
|
+
- goal and current status
|
|
119
|
+
- next actions
|
|
120
|
+
- decisions
|
|
121
|
+
- dead ends and failed approaches
|
|
122
|
+
- evidence and test outputs
|
|
123
|
+
- relevant files and source conclusions
|
|
124
|
+
- repo name, branch, commit hash, and git diff
|
|
125
|
+
- compact resume context under a rough token budget
|
|
126
|
+
|
|
127
|
+
The source cache is deliberately lightweight. Agentpack stores metadata, hashes, summaries, and optional snippets, not a full copy of the repository.
|
|
128
|
+
|
|
129
|
+
## Context Budgets
|
|
130
|
+
|
|
131
|
+
`--budget` is an approximate token target for generated handoff context. v0 uses a simple estimate, so the number is a practical target, not an exact API token count. Resume output includes estimated usage and a budget status line that says whether any sections were omitted or truncated.
|
|
132
|
+
|
|
133
|
+
`--query` is an optional local filter for Source Cache. It uses deterministic lexical matching, not embeddings or network calls. Matching sources keep their summaries and snippets; changed or missing source records are always shown in full; non-matching unchanged sources stay visible as compact stubs with path, short topic, hash status, meaning, and guidance. If nothing matches, Agentpack keeps the full Source Cache to avoid false-negative filtering.
|
|
134
|
+
|
|
135
|
+
Suggested defaults:
|
|
136
|
+
|
|
137
|
+
- `1200`: quick status ping
|
|
138
|
+
- `4000`: compact manual handoff
|
|
139
|
+
- `8000`: deeper coding-agent handoff
|
|
140
|
+
- `16000`: large debugging session or review
|
|
141
|
+
|
|
142
|
+
When unsure, start with:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
agentpack resume --preset quick --query "MCP install"
|
|
146
|
+
agentpack resume --preset agent --query "MCP install"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## MCP
|
|
150
|
+
|
|
151
|
+
`agentpack mcp` starts a stdio MCP server with tools for reading and writing task state:
|
|
152
|
+
|
|
153
|
+
- `load_context`
|
|
154
|
+
- `record_decision`
|
|
155
|
+
- `record_dead_end`
|
|
156
|
+
- `attach_evidence`
|
|
157
|
+
- `record_source`
|
|
158
|
+
- `source_status`
|
|
159
|
+
- `checkpoint`
|
|
160
|
+
- `resume`
|
|
161
|
+
- `diff`
|
|
162
|
+
- `replay`
|
|
163
|
+
|
|
164
|
+
See [docs/MCP.md](docs/MCP.md) for the current MCP contract and smoke-test flow.
|
|
165
|
+
|
|
166
|
+
## Roadmap
|
|
167
|
+
|
|
168
|
+
```text
|
|
169
|
+
v0: CLI + local source cache + markdown fallback export
|
|
170
|
+
v1: local MCP server + coding-agent installers
|
|
171
|
+
v2: stronger repo/file hashes, richer retrieval, and smarter budget packing
|
|
172
|
+
v3: shareable .agentpack bundle
|
|
173
|
+
v4: optional hosted sync/share
|
|
174
|
+
```
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
Agentpack is designed as a local-first developer tool. The default threat model assumes task state may contain source paths, command output, stack traces, and implementation notes that should stay on the developer machine.
|
|
4
|
+
|
|
5
|
+
## v0 Security Commitments
|
|
6
|
+
|
|
7
|
+
- No telemetry.
|
|
8
|
+
- No network calls in normal CLI or MCP operation.
|
|
9
|
+
- No dependency install or download during `agentpack install`.
|
|
10
|
+
- No `postinstall` script.
|
|
11
|
+
- No shell hooks installed silently.
|
|
12
|
+
- No source upload or hosted sync.
|
|
13
|
+
- No full repository copy in `.agentpack/` by default.
|
|
14
|
+
- Runtime package has zero third-party dependencies in v0.
|
|
15
|
+
|
|
16
|
+
## npm Supply Chain
|
|
17
|
+
|
|
18
|
+
The project uses a conservative npm setup:
|
|
19
|
+
|
|
20
|
+
- exact dependency versions
|
|
21
|
+
- committed lockfile
|
|
22
|
+
- `ignore-scripts=true` for installs
|
|
23
|
+
- TypeScript compiler only for builds
|
|
24
|
+
- npm provenance enabled for future public releases
|
|
25
|
+
- trusted publishing preferred over long-lived npm tokens
|
|
26
|
+
|
|
27
|
+
Before publishing, maintainers should run:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm ci
|
|
31
|
+
npm audit signatures
|
|
32
|
+
npm test
|
|
33
|
+
npm pack --dry-run
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Sensitive Data
|
|
37
|
+
|
|
38
|
+
Agentpack redacts common secret-looking values and configured environment variable values from generated context and key local records such as source summaries, evidence, checkpoints, replay output, and MCP context responses.
|
|
39
|
+
|
|
40
|
+
Redaction is best-effort, not a guarantee. Users should treat `.agentpack/` as project-sensitive data and review exported handoff files before sharing them.
|
|
41
|
+
|
|
42
|
+
## Reporting
|
|
43
|
+
|
|
44
|
+
Until a public repository security contact exists, report issues privately to the project maintainer.
|
|
Binary file
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runCli } from "./cli/index.js";
|
|
3
|
+
runCli(process.argv.slice(2), process.cwd()).catch((error) => {
|
|
4
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5
|
+
process.stderr.write(`agentpack: ${message}\n`);
|
|
6
|
+
process.exitCode = 1;
|
|
7
|
+
});
|
|
8
|
+
//# sourceMappingURL=agentpack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agentpack.js","sourceRoot":"","sources":["../../src/agentpack.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAExC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IACpE,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,OAAO,IAAI,CAAC,CAAC;IAChD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { createCheckpoint, diffCheckpoints } from "../core/checkpoints.js";
|
|
6
|
+
import { buildResume } from "../core/resume.js";
|
|
7
|
+
import { formatBudgetPresets, resolveBudget } from "../core/presets.js";
|
|
8
|
+
import { buildDoctorReport } from "../core/doctor.js";
|
|
9
|
+
import { redactForRoot } from "../core/redaction.js";
|
|
10
|
+
import { appendEvent, getPackPath, initPack, readState, requirePackRoot, withPackWriteLock, writeState } from "../core/store.js";
|
|
11
|
+
import { addEvidence, addSourceRecord, formatSourceStatuses, getSourceStatuses, replayEvents } from "../operations.js";
|
|
12
|
+
import { installIntegration } from "../integrations/install.js";
|
|
13
|
+
import { startMcpServer } from "../mcp/server.js";
|
|
14
|
+
export async function runCli(argv, cwd) {
|
|
15
|
+
const command = argv[0];
|
|
16
|
+
const rest = argv.slice(1);
|
|
17
|
+
if (!command || command === "--help" || command === "-h") {
|
|
18
|
+
printHelp();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (command === "--version" || command === "-v") {
|
|
22
|
+
process.stdout.write(`${readPackageVersion()}\n`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (command === "init") {
|
|
26
|
+
const packPath = initPack(cwd);
|
|
27
|
+
process.stdout.write(`Initialized Agentpack at ${packPath}\n`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (command === "mcp") {
|
|
31
|
+
const parsed = parseArgs(rest);
|
|
32
|
+
startMcpServer(resolveMcpStartDir(parsed.options, cwd));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (command === "doctor") {
|
|
36
|
+
const report = buildDoctorReport(cwd);
|
|
37
|
+
process.stdout.write(`${report.text}\n`);
|
|
38
|
+
process.exitCode = report.ok ? 0 : 1;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const root = requirePackRoot(cwd);
|
|
42
|
+
if (command === "status") {
|
|
43
|
+
const state = readState(root);
|
|
44
|
+
process.stdout.write(`${JSON.stringify(state, null, 2)}\n`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (command === "record") {
|
|
48
|
+
recordCommand(root, rest);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (command === "note") {
|
|
52
|
+
noteCommand(root, rest);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (command === "source") {
|
|
56
|
+
sourceCommand(root, rest);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (command === "evidence") {
|
|
60
|
+
evidenceCommand(root, rest);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (command === "run") {
|
|
64
|
+
runCommand(root, rest);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (command === "checkpoint") {
|
|
68
|
+
const parsed = parseArgs(rest);
|
|
69
|
+
const checkpoint = createCheckpoint(root, {
|
|
70
|
+
summary: stringOption(parsed.options.message) || stringOption(parsed.options.m) || stringOption(parsed.options.summary),
|
|
71
|
+
status: stringOption(parsed.options.status),
|
|
72
|
+
nextActions: toArray(parsed.options.next)
|
|
73
|
+
});
|
|
74
|
+
process.stdout.write(`Created checkpoint ${checkpoint.id}\n`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (command === "resume") {
|
|
78
|
+
const parsed = parseArgs(rest);
|
|
79
|
+
const resume = buildResume(root, {
|
|
80
|
+
budget: budgetOption(parsed.options),
|
|
81
|
+
query: stringOption(parsed.options.query)
|
|
82
|
+
});
|
|
83
|
+
process.stdout.write(`${resume.markdown}\n`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (command === "export") {
|
|
87
|
+
const parsed = parseArgs(rest);
|
|
88
|
+
const target = stringOption(parsed.options.to) || parsed.positionals[0] || "markdown";
|
|
89
|
+
const resume = buildResume(root, {
|
|
90
|
+
budget: budgetOption(parsed.options, 4000),
|
|
91
|
+
query: stringOption(parsed.options.query)
|
|
92
|
+
});
|
|
93
|
+
const filePath = exportPath(root, target);
|
|
94
|
+
writeFileSync(filePath, resume.markdown, "utf8");
|
|
95
|
+
process.stdout.write(`Exported ${target} handoff to ${filePath}\n`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (command === "diff") {
|
|
99
|
+
const parsed = parseArgs(rest);
|
|
100
|
+
const diff = diffCheckpoints(root, parsed.positionals[0], parsed.positionals[1]);
|
|
101
|
+
process.stdout.write(`${redactForRoot(root, diff)}\n`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (command === "replay") {
|
|
105
|
+
const parsed = parseArgs(rest);
|
|
106
|
+
const replay = replayEvents(root, numberOption(parsed.options.limit) || 50);
|
|
107
|
+
process.stdout.write(`${redactForRoot(root, replay)}\n`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (command === "install") {
|
|
111
|
+
const target = rest[0];
|
|
112
|
+
if (!target) {
|
|
113
|
+
throw new Error("install requires target: codex, claude, claude-desktop, or cursor");
|
|
114
|
+
}
|
|
115
|
+
const parsed = parseArgs(rest.slice(1));
|
|
116
|
+
const dryRun = installDryRun(parsed.options);
|
|
117
|
+
const message = installIntegration(root, target, { dryRun });
|
|
118
|
+
process.stdout.write(`${message}\n`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (command === "set") {
|
|
122
|
+
setCommand(root, rest);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
throw new Error(`Unknown command: ${command}`);
|
|
126
|
+
}
|
|
127
|
+
export function resolveMcpStartDir(options, cwd) {
|
|
128
|
+
return stringOption(options.root) || process.env.AGENTPACK_ROOT || cwd;
|
|
129
|
+
}
|
|
130
|
+
function printHelp() {
|
|
131
|
+
process.stdout.write(`Agentpack
|
|
132
|
+
|
|
133
|
+
Local task-state ledger for AI coding agents.
|
|
134
|
+
|
|
135
|
+
Usage:
|
|
136
|
+
agentpack init
|
|
137
|
+
agentpack set goal <text>
|
|
138
|
+
agentpack set status <text>
|
|
139
|
+
agentpack set next <item> [--next <item>]
|
|
140
|
+
agentpack source add <file> --summary <text>
|
|
141
|
+
agentpack source status [--json]
|
|
142
|
+
agentpack record decision <text>
|
|
143
|
+
agentpack record dead-end <text> --reason <text>
|
|
144
|
+
agentpack note <text>
|
|
145
|
+
agentpack evidence add --kind test-output --file test.log
|
|
146
|
+
agentpack run "npm test"
|
|
147
|
+
agentpack checkpoint -m <summary> --status <text> --next <item>
|
|
148
|
+
agentpack resume --preset agent [--query <text>]
|
|
149
|
+
agentpack export --to markdown --preset chat [--query <text>]
|
|
150
|
+
agentpack diff [from] [to]
|
|
151
|
+
agentpack replay
|
|
152
|
+
agentpack doctor
|
|
153
|
+
agentpack mcp [--root <path>]
|
|
154
|
+
agentpack install codex|claude|claude-desktop|cursor [--dry-run|--write]
|
|
155
|
+
agentpack --version
|
|
156
|
+
agentpack --help
|
|
157
|
+
|
|
158
|
+
MCP root resolution: --root, then AGENTPACK_ROOT, then current working directory.
|
|
159
|
+
Budget presets: ${formatBudgetPresets()}
|
|
160
|
+
`);
|
|
161
|
+
}
|
|
162
|
+
function recordCommand(root, rest) {
|
|
163
|
+
const type = rest[0];
|
|
164
|
+
const args = rest.slice(1);
|
|
165
|
+
const parsed = parseArgs(args);
|
|
166
|
+
const text = stringOption(parsed.options.text) || parsed.positionals.join(" ");
|
|
167
|
+
if (!isRecordType(type)) {
|
|
168
|
+
throw new Error("record type must be decision, dead-end, or note");
|
|
169
|
+
}
|
|
170
|
+
if (!text) {
|
|
171
|
+
throw new Error("record requires text");
|
|
172
|
+
}
|
|
173
|
+
const event = appendEvent(root, type, {
|
|
174
|
+
text: redactForRoot(root, text),
|
|
175
|
+
reason: redactForRoot(root, stringOption(parsed.options.reason) || ""),
|
|
176
|
+
files: toArray(parsed.options.file),
|
|
177
|
+
evidence: toArray(parsed.options.evidence)
|
|
178
|
+
});
|
|
179
|
+
process.stdout.write(`Recorded ${type} ${event.id}\n`);
|
|
180
|
+
}
|
|
181
|
+
function noteCommand(root, rest) {
|
|
182
|
+
const parsed = parseArgs(rest);
|
|
183
|
+
const text = stringOption(parsed.options.text) || parsed.positionals.join(" ");
|
|
184
|
+
if (!text) {
|
|
185
|
+
throw new Error("note requires text");
|
|
186
|
+
}
|
|
187
|
+
const event = appendEvent(root, "note", { text: redactForRoot(root, text) });
|
|
188
|
+
process.stdout.write(`Recorded note ${event.id}\n`);
|
|
189
|
+
}
|
|
190
|
+
function sourceCommand(root, rest) {
|
|
191
|
+
const subcommand = rest[0];
|
|
192
|
+
const args = rest.slice(1);
|
|
193
|
+
if (subcommand === "status") {
|
|
194
|
+
const parsed = parseArgs(args);
|
|
195
|
+
if (parsed.options.json) {
|
|
196
|
+
const statuses = JSON.stringify(getSourceStatuses(root), null, 2);
|
|
197
|
+
process.stdout.write(`${redactForRoot(root, statuses)}\n`);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
process.stdout.write(`${formatSourceStatuses(root)}\n`);
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (subcommand !== "add") {
|
|
205
|
+
throw new Error("source command supports `add` and `status`");
|
|
206
|
+
}
|
|
207
|
+
const parsed = parseArgs(args);
|
|
208
|
+
const filePath = parsed.positionals[0];
|
|
209
|
+
if (!filePath) {
|
|
210
|
+
throw new Error("source add requires a file path");
|
|
211
|
+
}
|
|
212
|
+
const source = addSourceRecord(root, filePath, {
|
|
213
|
+
summary: stringOption(parsed.options.summary) || stringOption(parsed.options.s) || parsed.positionals.slice(1).join(" "),
|
|
214
|
+
snippet: stringOption(parsed.options.snippet) || ""
|
|
215
|
+
});
|
|
216
|
+
process.stdout.write(`Recorded source ${source.path} (${source.hash.slice(0, 12)})\n`);
|
|
217
|
+
}
|
|
218
|
+
function evidenceCommand(root, rest) {
|
|
219
|
+
const subcommand = rest[0];
|
|
220
|
+
const args = rest.slice(1);
|
|
221
|
+
if (subcommand !== "add") {
|
|
222
|
+
throw new Error("evidence command supports only `add` in v0");
|
|
223
|
+
}
|
|
224
|
+
const parsed = parseArgs(args);
|
|
225
|
+
const event = addEvidence(root, {
|
|
226
|
+
kind: stringOption(parsed.options.kind) || "note",
|
|
227
|
+
file: stringOption(parsed.options.file),
|
|
228
|
+
content: stringOption(parsed.options.content) || parsed.positionals.join(" "),
|
|
229
|
+
command: stringOption(parsed.options.command),
|
|
230
|
+
exitCode: stringOption(parsed.options.exitCode)
|
|
231
|
+
});
|
|
232
|
+
process.stdout.write(`Attached evidence ${event.id}\n`);
|
|
233
|
+
}
|
|
234
|
+
function runCommand(root, rest) {
|
|
235
|
+
const command = rest.join(" ").trim();
|
|
236
|
+
if (!command) {
|
|
237
|
+
throw new Error("run requires a command");
|
|
238
|
+
}
|
|
239
|
+
const startedAt = new Date().toISOString();
|
|
240
|
+
const result = spawnSync(command, {
|
|
241
|
+
cwd: root,
|
|
242
|
+
shell: true,
|
|
243
|
+
encoding: "utf8",
|
|
244
|
+
maxBuffer: 10 * 1024 * 1024
|
|
245
|
+
});
|
|
246
|
+
const stdout = result.stdout || "";
|
|
247
|
+
const stderr = result.stderr || "";
|
|
248
|
+
if (stdout) {
|
|
249
|
+
process.stdout.write(stdout);
|
|
250
|
+
}
|
|
251
|
+
if (stderr) {
|
|
252
|
+
process.stderr.write(stderr);
|
|
253
|
+
}
|
|
254
|
+
const exitCode = typeof result.status === "number" ? result.status : 1;
|
|
255
|
+
const content = [
|
|
256
|
+
`Command: ${command}`,
|
|
257
|
+
`Started: ${startedAt}`,
|
|
258
|
+
`Exit code: ${exitCode}`,
|
|
259
|
+
"",
|
|
260
|
+
"## stdout",
|
|
261
|
+
stdout || "(empty)",
|
|
262
|
+
"",
|
|
263
|
+
"## stderr",
|
|
264
|
+
stderr || "(empty)"
|
|
265
|
+
].join("\n");
|
|
266
|
+
const event = addEvidence(root, {
|
|
267
|
+
kind: "command-output",
|
|
268
|
+
content,
|
|
269
|
+
command,
|
|
270
|
+
exitCode
|
|
271
|
+
});
|
|
272
|
+
process.stdout.write(`\nAttached command evidence ${event.id}\n`);
|
|
273
|
+
process.exitCode = exitCode;
|
|
274
|
+
}
|
|
275
|
+
function setCommand(root, rest) {
|
|
276
|
+
const field = rest[0];
|
|
277
|
+
const args = rest.slice(1);
|
|
278
|
+
const parsed = parseArgs(args);
|
|
279
|
+
const text = parsed.positionals.join(" ");
|
|
280
|
+
withPackWriteLock(root, () => {
|
|
281
|
+
const state = readState(root);
|
|
282
|
+
if (field === "goal") {
|
|
283
|
+
state.goal = redactForRoot(root, text);
|
|
284
|
+
}
|
|
285
|
+
else if (field === "status") {
|
|
286
|
+
state.currentStatus = redactForRoot(root, text);
|
|
287
|
+
}
|
|
288
|
+
else if (field === "next") {
|
|
289
|
+
state.nextActions = [text, ...toArray(parsed.options.next)]
|
|
290
|
+
.filter(Boolean)
|
|
291
|
+
.map((item) => redactForRoot(root, item));
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
throw new Error("set supports goal, status, or next");
|
|
295
|
+
}
|
|
296
|
+
writeState(root, state);
|
|
297
|
+
});
|
|
298
|
+
process.stdout.write(`Updated ${field}\n`);
|
|
299
|
+
}
|
|
300
|
+
function exportPath(root, target) {
|
|
301
|
+
const normalized = String(target).toLowerCase();
|
|
302
|
+
const fileName = normalized === "chatgpt" ? "chatgpt-handoff.md" : `${normalized}-handoff.md`;
|
|
303
|
+
const filePath = getPackPath(root, "exports", fileName);
|
|
304
|
+
if (!existsSync(path.dirname(filePath))) {
|
|
305
|
+
throw new Error("Agentpack exports directory is missing. Run `agentpack init` again.");
|
|
306
|
+
}
|
|
307
|
+
return filePath;
|
|
308
|
+
}
|
|
309
|
+
function parseArgs(args) {
|
|
310
|
+
const options = {};
|
|
311
|
+
const positionals = [];
|
|
312
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
313
|
+
const item = args[index];
|
|
314
|
+
if (!item) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (item.startsWith("--")) {
|
|
318
|
+
const [rawKey, inlineValue] = item.slice(2).split("=", 2);
|
|
319
|
+
if (!rawKey) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const value = inlineValue !== undefined ? inlineValue : args[index + 1];
|
|
323
|
+
if (inlineValue === undefined && value && !value.startsWith("-")) {
|
|
324
|
+
index += 1;
|
|
325
|
+
addOption(options, rawKey, value);
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
addOption(options, rawKey, true);
|
|
329
|
+
}
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (item.startsWith("-") && item.length > 1) {
|
|
333
|
+
const key = item.slice(1);
|
|
334
|
+
const value = args[index + 1];
|
|
335
|
+
if (value && !value.startsWith("-")) {
|
|
336
|
+
index += 1;
|
|
337
|
+
addOption(options, key, value);
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
addOption(options, key, true);
|
|
341
|
+
}
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
positionals.push(item);
|
|
345
|
+
}
|
|
346
|
+
return { options, positionals };
|
|
347
|
+
}
|
|
348
|
+
function addOption(options, key, value) {
|
|
349
|
+
if (options[key] === undefined) {
|
|
350
|
+
options[key] = value;
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (Array.isArray(options[key])) {
|
|
354
|
+
const textValue = stringOption(value);
|
|
355
|
+
if (textValue) {
|
|
356
|
+
options[key].push(textValue);
|
|
357
|
+
}
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
options[key] = [...toArray(options[key]), ...toArray(value)];
|
|
361
|
+
}
|
|
362
|
+
function toArray(value) {
|
|
363
|
+
if (value === undefined || value === true || value === false) {
|
|
364
|
+
return [];
|
|
365
|
+
}
|
|
366
|
+
return Array.isArray(value) ? value : [value];
|
|
367
|
+
}
|
|
368
|
+
function stringOption(value) {
|
|
369
|
+
if (value === undefined || value === true || value === false) {
|
|
370
|
+
return "";
|
|
371
|
+
}
|
|
372
|
+
return Array.isArray(value) ? value[0] || "" : value;
|
|
373
|
+
}
|
|
374
|
+
function numberOption(value) {
|
|
375
|
+
if (value === undefined || value === true || value === false) {
|
|
376
|
+
return 0;
|
|
377
|
+
}
|
|
378
|
+
const number = Number(value);
|
|
379
|
+
return Number.isFinite(number) ? number : 0;
|
|
380
|
+
}
|
|
381
|
+
function installDryRun(options) {
|
|
382
|
+
const dryRun = options["dry-run"] === true;
|
|
383
|
+
const write = options.write === true;
|
|
384
|
+
if (dryRun && write) {
|
|
385
|
+
throw new Error("install accepts either --dry-run or --write, not both");
|
|
386
|
+
}
|
|
387
|
+
return !write;
|
|
388
|
+
}
|
|
389
|
+
function budgetOption(options, fallback = 0) {
|
|
390
|
+
return resolveBudget({
|
|
391
|
+
budget: numberOption(options.budget),
|
|
392
|
+
preset: stringOption(options.preset)
|
|
393
|
+
}, fallback);
|
|
394
|
+
}
|
|
395
|
+
function isRecordType(value) {
|
|
396
|
+
return value === "decision" || value === "dead-end" || value === "note";
|
|
397
|
+
}
|
|
398
|
+
function readPackageVersion() {
|
|
399
|
+
// Resolve package.json relative to the compiled CLI file (dist/src/cli/index.js).
|
|
400
|
+
// Works both in the source tree and after `npm install -g`, because the package
|
|
401
|
+
// root is always three levels above this file.
|
|
402
|
+
try {
|
|
403
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
404
|
+
const pkgPath = path.resolve(here, "..", "..", "..", "package.json");
|
|
405
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
406
|
+
return pkg.version ?? "unknown";
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
return "unknown";
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
//# sourceMappingURL=index.js.map
|