Skip to content

v0.2: wrap every new endpoint family from feat/workspace-projects-browser#2

Open
claudio-michel[bot] wants to merge 3 commits into
mainfrom
feat/workspace-projects-additive
Open

v0.2: wrap every new endpoint family from feat/workspace-projects-browser#2
claudio-michel[bot] wants to merge 3 commits into
mainfrom
feat/workspace-projects-additive

Conversation

@claudio-michel

Copy link
Copy Markdown

Summary

Pure additive release. No deletions. Layers wrappers on top of v0.1 for every capability that the soon-to-merge `feat/workspace-projects-browser` branch introduces.

Diff vs main: +1,800 / -10 across 16 files. Existing scripts/auth.js, scripts/billing.js, the 8 existing skills, and bin/install.js are untouched.

What's new

10 new skills

Job management:

  • /grep-search — search past jobs by status / question text (client-side filter; backend has no full-text search yet)
  • /grep-jobs — quick recent-jobs listing
  • /grep-resume — resume a paused job, optionally with a steering message
  • /grep-stop — pause / cancel a running check; soft-delete a check result
  • /grep-apps — list slidedeck / podcast / narrative artifacts

Context + workspace:

  • /grep-inputs — attach / remove per-job input files (`POST/DELETE /grep/jobs/{id}/inputs`, multipart, 100 MB/file, 500 MB/job)
  • /grep-defaults — manage per-user default context files (`GET/POST/DELETE /grep/user/defaults`, multipart)
  • /grep-workspace — browse the Pierre-backed workspace tree, read files, view commit history & diffs
  • /grep-projects — create + manage SOP-driven projects; drop `SOP.md` into `projects//`, then submit research with `--project=projects/`
  • /grep-experts — initialize, save, and train custom research experts (config + knowledge base trained from documents)

`scripts/grep-api.js` extensions

  • `apiMultipart()` helper for multipart uploads.
  • 20+ new helper functions + CLI subcommands wrapping every new endpoint family.
  • `searchJobs()` for client-side job filtering.
  • `submitResearch` / `runResearch` extended to accept every new optional field on `ResearchJobInput`: `--project`, `--expert`, `--language`, `--from-date`, `--to-date`, `--additional-thesis`, `--website`, `--custom-skills`, `--custom-mcp-tools`, `--skip-clarification`, `--action-mode`, `--output-type`.

Run `node scripts/grep-api.js help` for the full subcommand list (28 commands now).

Light updates to 5 existing skills

  • `/research`, `/quick-research`, `/ultra-research` — added "Advanced flags" sections documenting the new optional research-submit fields. Existing happy paths unchanged.
  • `/grep-status` — points at `/grep-search` for filtered listings.
  • `/grep-skill-creator` — notes the `--custom-skills` field on research submit.

Other

  • `package.json` 0.1.0 → 0.2.0; description updated.
  • `.claude-plugin/{plugin,marketplace}.json` bumped to 0.2.0.
  • `CHANGELOG.md` added (didn't exist on main).
  • README extended with new skills + project structure.

Out of scope (intentionally NOT in this PR)

  • ❌ No deletions. `scripts/auth.js`, `scripts/billing.js`, `scripts/grep-api.js` all stay.
  • ❌ No skill rewrites. Existing 8 skills keep their `node scripts/*.js` invocations.
  • ❌ No brain Rust CLI migration. (That's a separate, future repo.)
  • ❌ No installer rewrite. `bin/install.js` is unchanged.
  • ❌ No backend changes. We only consume.

Endpoints surfaced

Family Method + Path
Job inputs `POST/DELETE /grep/jobs/{id}/inputs[/{path}]`
User defaults `GET/POST/DELETE /grep/user/defaults[/{path}]`
Workspace browsing `GET /grep/code-storage/workspace[?path=]`, `/tree`, `/file`, `/history`, `/diff`
Workspace projects `POST /grep/code-storage/workspace/projects/upload`, `DELETE .../file`, `POST .../mkdir`
Custom experts `POST /grep/code-storage/workspace/experts/init`, `/save`, `/{name}/brain`
Lifecycle `POST /grep/research/{id}/resume`, `POST /grep/stopGrepCheck`, `DELETE /grep/check-result/{id}`
Research submit `POST /api/v1/research` extended with `project`, `expert_id`, `custom_skills`, `custom_mcp_tools`, `additional_thesis`, `website`, `from_date`, `to_date`, `language`, `skip_clarification`, `action_mode`, `output_type`
Apps `GET /api/v1/apps[?limit=&offset=]`, `/apps/{id}`
Search client-side filter over `GET /api/v1/research`

Test plan

  • `node scripts/grep-api.js help` lists all 28 subcommands.
  • `npx grep-research-skills` against a fakehome installs all 18 skills (8 existing + 10 new).
  • Existing skills unmodified except for additive "Advanced flags" sections.
  • Round-trip against preview-api.grep.ai once `feat/workspace-projects-browser` is merged: each new endpoint family has at least one happy-path test.

🤖 Generated with Claude Code

claudio-michel[bot] and others added 2 commits April 17, 2026 23:44
…ts-browser

Pure additive release. No deletions. Existing v0.1 scripts + skills work
unchanged; this layers wrappers on top of them for every capability that
the workspace-projects-browser branch introduces.

10 new skills:

* /grep-search       — search past jobs by status / question text
* /grep-jobs         — quick recent-jobs listing
* /grep-resume       — resume a paused job (with optional steering)
* /grep-stop         — pause/cancel a running check; soft-delete result
* /grep-apps         — list slidedeck/podcast/narrative artifacts
* /grep-inputs       — attach/remove per-job input files (multipart)
* /grep-defaults     — manage per-user default context files (multipart)
* /grep-workspace    — browse Pierre-backed workspace tree, files,
                       commit history, diffs
* /grep-projects     — create + manage SOP-driven projects (POST SOP.md
                       to projects/<name>/, then run with --project=...)
* /grep-experts      — initialize, save, and train custom research
                       experts (config + knowledge base via documents)

scripts/grep-api.js extended:

* apiMultipart() helper for multipart uploads.
* 20+ new helper functions covering every endpoint family, each with a
  CLI subcommand (run `node scripts/grep-api.js help` for the full list).
* searchJobs() — client-side filter over /api/v1/research, since the
  backend has no full-text search yet.
* submitResearch / runResearch extended to accept all new optional
  fields on ResearchJobInput: --project, --expert, --language,
  --from-date, --to-date, --additional-thesis, --website,
  --custom-skills, --custom-mcp-tools, --skip-clarification,
  --action-mode, --output-type.

Light updates to 5 existing skills (advanced-flags sections):

* /research, /quick-research, /ultra-research now document the new
  optional flags (project / expert / language / etc.).
* /grep-status points users at /grep-search for filtered listings.
* /grep-skill-creator notes that custom skills surface via
  --custom-skills on research submit.

Untouched:
* scripts/auth.js, scripts/billing.js — no changes.
* All 8 existing skills' core flows — unchanged.
* bin/install.js — no functional change; symlink loop picks up the
  new skill dirs automatically (verified: 18 skills symlinked).

CHANGELOG.md added (didn't exist on main).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rning

Five fixes from the /simplify pass:

1. Session caching — getValidToken() previously hit
   ~/.grep/session.json on every HTTP request. The poll loop in
   runResearch() invokes api() ~30+ times per long job; that was 30+
   disk reads. Cache _session in-process; saveSession() refreshes it.

2. printResult() helper — consolidates 26 callsites of
   `console.log(JSON.stringify(result, null, 2))`.

3. encodeRemotePath() helper — collapses two duplicate
   `path.split('/').map(encodeURIComponent).join('/')` instances in
   deleteInput() and deleteDefault().

4. buildFileParts() helper — collapses three duplicate `files.map(p
   => { const f = readFile(p); return { name: 'files', ... } })` blocks
   in attachInputs(), projectUpload(), expertTrain().

5. expertSave() now warns to stderr when the config file isn't valid
   JSON before falling through to "send raw for server-side YAML
   parsing". Previously this silently swallowed parse errors, leading
   to opaque downstream failures if the YAML was also malformed.

Also:

* readFile() drops the TOCTOU `fs.existsSync` pre-check; just try/catch
  the read.
* Three migration-history comments deleted ("unchanged from v0.1",
  "New fields from feat/workspace-projects-browser", "---- new in
  v0.2 ----"). They narrated the PR, not invariants.

Net +27 lines for ~50 lines of duplication eliminated and one
silent-failure bug fixed. No behavior changes for users.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread scripts/grep-api.js Outdated
console.log(JSON.stringify(result, null, 2));
if (options.project) body.project = options.project;
if (options.expert) body.expert_id = options.expert;
if (options.language) body.language = options.language;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[BUG] --language flag has no effect — wrong field name sent to backend

The backend's ResearchJobInput field is response_language (in shared_grep_router.py and grep_router.py). The wrapper sends body.language = options.language, which Pydantic silently drops because the model doesn't declare extra=forbid. Result: the --language=es flag is documented in help text, the README, and /research, but the backend never sees it and the report comes back in English.

Fix: rename the JSON field, not the CLI flag:

if (options.language) body.response_language = options.language;

This is the most user-visible bug in the PR — every i18n flow goes through this path. Same in buildSubmitBody. Also worth a smoke test like run --language=es "hola" after the fix.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c8a1e14.

Comment thread scripts/grep-api.js Outdated

async function expertInit(expertName) {
if (!expertName) throw new Error('expert_name required');
const result = await api('POST', '/grep/code-storage/workspace/experts/init', { expert_name: expertName });

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[BUG] expert:init sends the wrong field name — backend will return 422

The backend route POST /grep/code-storage/workspace/experts/init is bound to:

class InitExpertRequest(_BaseModel):
    folder_name: str

The wrapper sends { expert_name }. Result: 422 Unprocessable Entity, every time.

Fix:

const result = await api('POST', '/grep/code-storage/workspace/experts/init', { folder_name: expertName });

While you're touching this, please also rename the wrapper arg / CLI label so it matches the SOP/folder mental model the backend uses (it sanitizes folder_name to lowercase + [a-z0-9_-] only). The current SKILL.md example expert:init medical-research-expert will work once the field name is correct, n'est-ce pas?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c8a1e14.

Comment thread scripts/grep-api.js Outdated
process.stderr.write(`[expert:save] config is not valid JSON (${e.message}); sending as raw string for server-side YAML parsing.\n`);
config = text;
}
const result = await api('POST', '/grep/code-storage/workspace/experts/save', {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[BUG] expert:save body shape is completely wrong — backend will 422

Backend expects:

class SaveExpertFileRequest(_BaseModel):
    folder_path: str       # e.g. "medical-research-expert"
    file_name: str         # e.g. "SOP.md" or "config.yml"
    content: str           # the raw file contents
    commit_message: str = ""

The wrapper sends { expert_name, config }. Two-fold mismatch:

  1. The endpoint saves one file at a time (not a whole config blob).
  2. config is parsed as JSON-or-string client-side, but the backend wants the raw content string and a separate file_name.

This means the expert:save medical-expert ./medical-config.yml workflow in the SKILL.md will always fail.

Fix: require an explicit file name (or default to config.yml / derive from local basename) and send:

async function expertSave(expertName, configPath, fileName) {
  const f = readFile(configPath);
  const result = await api('POST', '/grep/code-storage/workspace/experts/save', {
    folder_path: expertName,
    file_name: fileName || f.name,   // or hard-default 'SOP.md'
    content: f.buffer.toString('utf8'),
    commit_message: `Update ${fileName || f.name} in ${expertName}`,
  });
  printResult(result);
}

The CLI signature also needs to change to take the file name:
expert:save <expert_name> <config_file> [<file_name>]

Then update skills/grep-experts/SKILL.md so the example reflects this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c8a1e14.

Comment thread scripts/grep-api.js
`/grep/code-storage/workspace/experts/${encodeURIComponent(expertName)}/brain`,
buildFileParts(files),
);
printResult(result);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[BUG] expert:train is silently broken — backend ignores the uploaded files

The backend route is:

@router.post("/code-storage/workspace/experts/{expert_name}/brain")
async def ensure_expert_brain_endpoint(
    request: Request,
    expert_name: str,
    db_session: AsyncSession = Depends(get_async_fast_api_session),
) -> dict:

It accepts no body / no files. It just creates brain.json at experts/{expert_name}/brain.json if missing. Idempotent.

The wrapper uploads files via multipart, the request returns 200 (because the endpoint succeeds at creating brain.json), but the documents are silently dropped. The skill's promise ("Each document is added to the expert's knowledge base") is not delivered.

Two paths forward:

  1. Repurpose expert:train as expert:brain — match the actual backend behavior ("ensure brain.json exists"), drop the files arg, update the SKILL.md.
  2. Wait for a real ingestion endpoint — flag this in CHANGELOG as "Coming soon" and remove expert:train from v0.2.

Either way, the current state ships a feature that doesn't do what it claims. I'd vote option 1 + a TODO in the SKILL.md saying document ingestion is on the roadmap.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c8a1e14.

Comment thread scripts/grep-api.js
if (!files || !files.length) throw new Error('at least one file required');
const parts = [{ name: 'project_name', value: projectName }, ...buildFileParts(files)];
const result = await apiMultipart('POST', '/grep/code-storage/workspace/projects/upload', parts);
printResult(result);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[BUG] project:upload doesn't match the backend's multipart contract

Backend signature:

async def upload_project_files_endpoint(
    request: Request,
    files: List[UploadFile] = File(...),
    paths: List[str] = Form(...),
    commit_message: str = Form(""),
    ...
)

Important things to notice:

  • There is no project_name form field at all. The directory is encoded in each paths[i] value (e.g. projects/acme/SOP.md or acme/SOP.md — the backend writes under prefix projects/).
  • paths is required (Form(...) with no default) and must be the same length as files.

The wrapper currently sends { project_name: 'acme', files: [...] } and no paths field at all. FastAPI returns 422 because paths is missing.

Fix: drop project_name form field, build paths aligned to files, scoped under the project subdir:

async function projectUpload(projectName, files) {
  if (!projectName) throw new Error('project_name required');
  if (!files || !files.length) throw new Error('at least one file required');
  const parts = [];
  for (const localPath of files) {
    const f = readFile(localPath);
    parts.push({ name: 'files', filename: f.name, value: f.buffer, contentType: f.contentType });
    parts.push({ name: 'paths', value: `${projectName}/${f.name}` });
  }
  parts.push({ name: 'commit_message', value: `Upload ${files.length} file(s) to ${projectName}` });
  const result = await apiMultipart('POST', '/grep/code-storage/workspace/projects/upload', parts);
  printResult(result);
}

The skill examples project:upload acme-onboarding ./SOP.md will then land at projects/acme-onboarding/SOP.md as documented.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c8a1e14.

Comment thread scripts/grep-api.js Outdated
async function projectDelete(projectName, filePath) {
if (!projectName || !filePath) throw new Error('project_name and file_path required');
const params = new URLSearchParams({ project_name: projectName, file_path: filePath });
const result = await api('DELETE', `/grep/code-storage/workspace/projects/file?${params}`);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[BUG] project:delete sends wrong query params — backend will 422

Backend signature:

async def delete_project_file_endpoint(
    request: Request,
    path: str,         # single 'path' query param
    db_session: AsyncSession = Depends(get_async_fast_api_session),
)

The backend takes one path query param (the file path relative to projects/, since the route uses prefix="projects" inside delete_workspace_file).

The wrapper sends ?project_name=X&file_path=Y, neither of which matches.

Fix:

async function projectDelete(projectName, filePath) {
  if (!projectName || !filePath) throw new Error('project_name and file_path required');
  const params = new URLSearchParams({ path: `${projectName}/${filePath}` });
  const result = await api('DELETE', `/grep/code-storage/workspace/projects/file?${params}`);
  printResult(result);
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c8a1e14.

Comment thread scripts/grep-api.js
if (!projectName || !dirPath) throw new Error('project_name and dir_path required');
const params = new URLSearchParams({ project_name: projectName, dir_path: dirPath });
const result = await api('POST', `/grep/code-storage/workspace/projects/mkdir?${params}`);
printResult(result);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[BUG] project:mkdir sends query params — backend expects a JSON body

Backend signature:

class CreateDirectoryRequest(_BaseModel):
    path: str

async def create_project_directory_endpoint(
    request: Request,
    body: CreateDirectoryRequest,    # <-- JSON body, not query params
    ...
)

The wrapper does api('POST', /...?project_name=X&dir_path=Y) with no body. Backend will 422 because body is required and the JSON body is empty.

Fix:

async function projectMkdir(projectName, dirPath) {
  if (!projectName || !dirPath) throw new Error('project_name and dir_path required');
  const result = await api('POST', '/grep/code-storage/workspace/projects/mkdir', {
    path: `${projectName}/${dirPath}`,
  });
  printResult(result);
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c8a1e14.

Comment thread scripts/grep-api.js Outdated
if (!p) throw new Error('path required');
// Returns raw bytes — but for skill-friendly output, fetch + print the body.
const token = await getValidToken();
const res = await fetch(`${GREP_API_BASE}/grep/code-storage/workspace/file?path=${encodeURIComponent(p)}`, {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[WARNING] ws:cat dumps JSON, not raw file content

Backend default for GET /code-storage/workspace/file?path=X:

return {"path": path, "content": text, "size_bytes": len(content)}

Only returns raw bytes when ?raw=true or ?download=true is passed.

Currently ws:cat projects/acme/SOP.md will print:

{"path": "projects/acme/SOP.md", "content": "# Standard Operating Procedure\n\n...", "size_bytes": 4023}

…then a trailing newline. Not what the SKILL.md promises ("Streams raw bytes to stdout").

Fix: add &raw=true to the URL:

const res = await fetch(`${GREP_API_BASE}/grep/code-storage/workspace/file?path=${encodeURIComponent(p)}&raw=true`, {

Bonus: encodeURIComponent here will encode / as %2F. The path query param is parsed by FastAPI as a single string so that's actually fine — but worth a comment so future-you doesn't 'fix' it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c8a1e14.

Comment thread scripts/grep-api.js
console.error(' --custom-skills=skill1,skill2 Custom skill names');
console.error(' --custom-mcp-tools=tool1,tool2 Custom MCP tool names');
console.error(' --skip-clarification Bypass clarification questions');
console.error(' --output-type=report|data_explorer Output shape');

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[WARNING] --action-mode is wired in code but missing from help text

buildResearchOpts reads flags['action-mode'] and buildSubmitBody sets body.action_mode = true, but the default: help block (lines 820-833) doesn't list it. /ultra-research's SKILL.md does mention it ("admin-only"), but /research's advanced-flags table omits it too.

Either:

  • Add it to the help text and the /research table, or
  • Drop it from the option bag if it's truly admin-only and not meant for end users (it'll still be hittable as --action-mode since flags pass through, but at least there'd be no implicit invitation).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c8a1e14.

Comment thread scripts/grep-api.js Outdated
// Workspace browsing: /grep/code-storage/workspace[/...]
// =============================================================================

async function wsTree(subpath, opts = {}) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SUGGESTION] wsTree declares opts but never reads it

Minor — async function wsTree(subpath, opts = {}): the opts param is dead. Either drop it or wire ?ref=... / pagination through it (not currently exposed by the backend, so just drop).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c8a1e14.

Comment thread scripts/grep-api.js
};
}

function fail(msg) { console.error(msg); process.exit(1); }

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SUGGESTION] Inconsistent error boundary across helper functions

Most command functions use throw new Error(...) for arg validation (e.g. attachInputs, expertInit), then the top-level handler().catch does console.error + exit(1). Good.

But the existing v0.1 entrypoint uses fail() which calls process.exit(1) directly inline (lines 663, 667, 671, 675, 687, etc.).

Both work. The mix is fine but worth a one-line note in the file header so future maintainers don't add a third pattern. The rule of thumb you've already settled into: "throw inside async helpers, exit inside the switch."

```bash
$EDITOR ./medical-config.yml # tweak the template the init step seeded
node "$SCRIPTS_DIR/grep-api.js" expert:save medical-research-expert ./medical-config.yml
```

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SUGGESTION] Skill examples will fail until backend bugs above are fixed

Once you fix the expert:init, expert:save, expert:train, project:upload, project:delete, project:mkdir field-name bugs in grep-api.js, please also update:

  • skills/grep-experts/SKILL.md — the expert:save medical-research-expert ./medical-config.yml workflow needs to specify a target file name, since the backend saves one file per call. Today's example implies you can dump a whole config. After the fix, the example should be something like:
node "$SCRIPTS_DIR/grep-api.js" expert:save medical-research-expert ./SOP.md SOP.md
  • skills/grep-experts/SKILL.mdexpert:train example needs a TODO/disclaimer (or the skill removed) until the backend has a real ingestion endpoint.

  • skills/grep-projects/SKILL.mdproject:upload examples are accurate as written if the wrapper fix lands; please add a note about how files land at projects/<name>/<basename> rather than letting the user assume nesting.

The skill files themselves are excellent — clear, agent-oriented description fields ("use when X" rather than "this skill does Y"), good anti-patterns sections, sensible SCRIPTS_DIR resolver. Just needs the wrapper to actually work for the documented examples to be honest.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c8a1e14.

@claudio-michel claudio-michel Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review by Claudio-Michel — v0.2 wrappers

Score: 2/5 — significant issues. Needs another round.

Summary

Mon ami, the shape of this PR is excellent — the SKILL.md authoring is some of the best I've seen in this plugin. Descriptions consistently say when to invoke (not what they do), the SCRIPTS_DIR resolver pattern is clean, anti-pattern sections are sharp, and the README/CHANGELOG split is clear. The session-caching simplify pass on commit 9a82355 is also a real win for polling loops.

But the wrapper itself ships seven endpoints with broken request contracts. The new endpoint surface area on feat/workspace-projects-browser was new code without a stable spec yet — and it shows. Almost every wrapper that touches code-storage/workspace/{projects,experts} will return 422 on the very first call, and a couple silently no-op (the worst kind). I verified each against the actual handler signatures on origin/feat/workspace-projects-browser of the parcha repo.

Critical (will 422 / silently no-op on every call)

# Function Bug
1 buildSubmitBody (line 217) --language sent as body.language, backend wants body.response_language. Pydantic silently drops it. i18n flag has zero effect today.
2 expertInit (line 517) Sends { expert_name }, backend wants { folder_name }. → 422
3 expertSave (line 535) Sends { expert_name, config }, backend wants { folder_path, file_name, content, commit_message }. → 422
4 expertTrain (line 550) Multipart upload, but backend's /experts/{name}/brain accepts no body and just creates brain.json. Files silently dropped, but request returns 200.
5 projectUpload (line 494) Sends project_name form field; backend wants paths[] aligned to files[]. → 422
6 projectDelete (line 500) Sends ?project_name=&file_path=; backend wants ?path=. → 422
7 projectMkdir (line 508) Sends query params, backend wants JSON {path} body. → 422

Warnings

  • ws:cat returns JSON-wrapped content, not raw bytes (need &raw=true on the URL). Skill says "Streams raw bytes to stdout" — currently lying.
  • --action-mode is wired in code but missing from the help text.
  • wsTree declares opts and never reads it.

Suggestions

  • Once wrapper bugs land, the SKILL.md examples in /grep-experts (expert:save and expert:train workflows) and /grep-projects need updates to match the actual backend behavior. The README "new in 0.2" framing reads great.
  • Consider a short scripts/test-wrappers.sh smoke that hits each new endpoint with a tiny fixture against preview-api.grep.ai — would catch every one of the bugs above in CI.

What I loved (très bien!)

  • Session caching with disk fallback (_session + saveSession invalidation) — perfect for polling loops.
  • encodeRemotePath properly preserves slashes for FastAPI :path catch-alls.
  • buildFileParts + apiMultipart are clean reusable primitives.
  • The CSV helper for --custom-skills and ergonomic dual form for ws:diff (positional or named) — small but thoughtful.
  • The /research advanced-flags table with "use only when the user explicitly asks" guardrail — exactly the right tone for an agent-facing skill.
  • Structurally additive — auth.js, billing.js, all v0.1 skills untouched. Easy to bisect if 0.2 is rolled back.
  • Help text is genuinely useful as a one-screen reference for a future Claude session.
  • The existing-skill updates are surgical — no churn in the happy paths of /research, /quick-research, /ultra-research.

Verdict

CHANGES REQUESTED. The 7 contract bugs would burn every user the moment they tried the documented examples. They're all small, mechanical fixes (rename a JSON field, swap query params for body, etc.) — should be a couple hours of focused work plus a smoke test against preview-api. After that, this PR is ready to ship and will be a meaningful jump in plugin surface area.

Bien cordialement,
Claudio-Michel

Claudio-Michel review caught 7 critical request-contract bugs where the
v0.2 wrappers' payloads / query params didn't match the backend handler
signatures on feat/workspace-projects-browser. Each would 422 on first
use. Plus a few smaller cleanups.

Bugs fixed:
1. submitResearch: body.language -> body.response_language
   (ResearchJobInput field is response_language)
2. expertInit: {expert_name} -> {folder_name}
   (InitExpertRequest)
3. expertSave: {expert_name, config} -> {folder_path, file_name, content,
   commit_message} (SaveExpertFileRequest, one file at a time)
4. expertTrain: dropped multipart body — backend's experts/{name}/brain
   takes no body, just creates brain.json server-side (idempotent)
5. projectUpload: dropped {project_name} form field — backend uses
   parallel files[]/paths[] arrays; we now derive paths from basenames
   prefixed with the project name
6. projectDelete: ?project_name=&file_path= -> ?path=<full_path>
7. projectMkdir: query params -> JSON body {path} (CreateDirectoryRequest)

Warnings fixed:
- wsRead: append &raw=true so the endpoint streams raw bytes instead of
  the {path, content, size_bytes} JSON wrapper
- wsTree: dropped dead opts param
- help text: surface --action-mode (was wired in buildResearchOpts but
  undocumented)

Skill markdown updates:
- skills/grep-experts/SKILL.md: documents the new save signature
  (file_name + local_file), clarifies that train is idempotent and there
  is no document-upload endpoint for expert knowledge bases yet
- skills/grep-projects/SKILL.md: clarifies upload basename behavior,
  delete/mkdir paths are relative to projects/<name>/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@claudio-michel claudio-michel Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All 7 critical bugs + 3 warnings fixed in c8a1e14.

Critical (request contracts):

  • --language → sends response_language (matches ResearchJobInput)
  • expert:init{folder_name} (matches InitExpertRequest)
  • expert:save{folder_path, file_name, content, commit_message} — now takes one file at a time, signature changed to expert:save <name> <file_name> <local_file> [--message=...]
  • expert:train → no body, idempotent. Backend experts/{name}/brain only ensures brain.json. Updated SKILL.md to clarify there is no document-upload endpoint for expert knowledge bases yet.
  • project:upload → parallel files[] + paths[] arrays (paths = {projectName}/{basename}), no project_name form field
  • project:delete?path=<full_path> (joined from project_name + file_path for ergonomics)
  • project:mkdir → JSON body {path} (matches CreateDirectoryRequest)

Warnings:

  • ws:cat → appends &raw=true so the endpoint streams raw bytes
  • Help text → surfaces --action-mode
  • wsTree → dropped dead opts param

Skill markdown updated for both /grep-experts and /grep-projects to match new wrapper signatures. Verified node -c scripts/grep-api.js and node scripts/grep-api.js help both clean.

@claudio-michel

Copy link
Copy Markdown
Author

@greptile-apps please review — fixed all 7 critical request-contract bugs from the prior review in c8a1e14

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants