feat(setup): --create [name] — provision a new project during agent setup - #159
Conversation
…setup `--project <id>` binds the directory to an existing project; a fresh machine with no project yet still needed a second command. `--create` runs the same `insta project create` path in-process, and `planProject` rejects the one request that cannot succeed — both flags at once — before the CLI, skill or MCP have landed. Name resolution stays in `projectCreate`: a generic cwd gets that command's guidance rather than aborting a setup that has installed nothing yet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Reviewed by Yang Dong
This adds project provisioning to agent setup by routing --create through the existing creation flow. Named projects work, but the nameless form creates incorrectly named projects on native Windows, so I would not merge it as it stands.
Nameless --create uses the full Windows path as the project name
important · defect · correctness · src/commands/setup.ts:475
When --create has no name, this passes undefined to projectCreate, whose pre-existing resolver extracts the directory using only /. On native Windows, a cwd such as C:\Users\me\my-app therefore becomes c-users-me-my-app rather than my-app, and that wrong name is provisioned and linked; this change depends on that pre-existing problem not existing. Use node:path’s platform-aware basename for both cwd and home resolution.
Evidence
read-the-code — src/index.ts:91-97, src/commands/setup.ts:385-400, src/commands/setup.ts:467-475, src/commands/project.ts:39-58, src/commands/project.ts:68-81, test/create-name.test.ts:9-27, .claude/skills/developing-insta-cli/SKILL.md:40-47
There was a problem hiding this comment.
Reviewed by Wang Miao
--create [name] extends the existing --project one-liner path so setup can provision a fresh project in the same process, with the flag pair rejected up front (before the env switch and CLI install) and creation failures stopping setup with exit 1 — the same contract --project already had. The plan/execute split is tested at both ends, all 672 tests and typecheck pass here, and the create path reuses projectCreate rather than re-implementing provisioning. I'd merge it; one cosmetic issue below that doesn't block.
The printed retry command echoes the raw --create value, not the name that would actually be created
minor · defect · correctness · src/commands/setup.ts:469
retry interpolates project.name verbatim, but projectCreate resolves the name through slugifyName (src/commands/project.ts:39-41). For insta setup agent --create "My App" with no session, the hint prints insta project create My App; commander 12 keeps excess args, so pasting it creates a project called my rather than the my-app setup would have made — a silently different name, not an error. Common single-token values are already slugs so this is invisible for the console-generated one-liner; interpolating slugifyName(project.name) (or quoting it) makes the two paths agree.
Evidence
read-the-code — src/commands/setup.ts:462-482, src/commands/project.ts:39-68, src/index.ts:113 (project create [name]); verified commander 12.1.0 excess-argument behaviour against the vendored node_modules/commander.
There was a problem hiding this comment.
3 issues found across 3 files
You’re at about 93% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/commands/setup.ts">
<violation number="1" location="src/commands/setup.ts:400">
P2: On native Windows, bare `--create` provisions a name derived from the entire drive path rather than this directory because `projectCreate` receives `undefined`. Make the default-name resolution platform-safe before using this callback.</violation>
<violation number="2" location="src/commands/setup.ts:469">
P2: When a named create fails, the printed retry command is not equivalent for names containing spaces or shell punctuation. Emit the normalized slug (or shell-escape the name) instead of interpolating `project.name` directly.</violation>
<violation number="3" location="src/commands/setup.ts:475">
P2: If `projectCreate` fails after its POST, this catch tells the user to create the project again even though the server-side project already exists. Preserve or surface the created project ID and direct recovery to linking it, rather than retrying a non-idempotent create.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| try { | ||
| await link(opts.project) | ||
| if (project.kind === 'link') await link(project.id) | ||
| else await create(project.name) |
There was a problem hiding this comment.
P2: If projectCreate fails after its POST, this catch tells the user to create the project again even though the server-side project already exists. Preserve or surface the created project ID and direct recovery to linking it, rather than retrying a non-idempotent create.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/commands/setup.ts, line 475:
<comment>If `projectCreate` fails after its POST, this catch tells the user to create the project again even though the server-side project already exists. Preserve or surface the created project ID and direct recovery to linking it, rather than retrying a non-idempotent create.</comment>
<file context>
@@ -441,24 +457,27 @@ export async function setupAgent(
try {
- await link(opts.project)
+ if (project.kind === 'link') await link(project.id)
+ else await create(project.name)
} catch (e) {
// Stop here — like the skill-install failure above, finishing with the success summary
</file context>
There was a problem hiding this comment.
Declining — the window is one non-idempotent line, and it is identical on insta project create.
After the POST, projectCreate calls writeProject, then tryInstallObserve (wrapped in try/catch, project.ts:19-27) and installSkills (whose entire body is inside a try/catch, ensure-skills.ts:80-102) — neither can throw. Only writeProject can, so the reachable case is a project created server-side with ./.insta/project.json unwritten.
That case predates this PR: insta project create my-app fails the same way, and rerunning it is the same second project. Distinguishing pre- from post-POST failure means restructuring projectCreate, which belongs with that command rather than with a flag that calls it.
| // code instead of pretending setup succeeded. | ||
| if (project.kind !== 'none') { | ||
| const linking = project.kind === 'link' | ||
| const retry = linking ? `insta project link ${project.id}` : `insta project create${project.name ? ` ${project.name}` : ''}` |
There was a problem hiding this comment.
P2: When a named create fails, the printed retry command is not equivalent for names containing spaces or shell punctuation. Emit the normalized slug (or shell-escape the name) instead of interpolating project.name directly.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/commands/setup.ts, line 469:
<comment>When a named create fails, the printed retry command is not equivalent for names containing spaces or shell punctuation. Emit the normalized slug (or shell-escape the name) instead of interpolating `project.name` directly.</comment>
<file context>
@@ -441,24 +457,27 @@ export async function setupAgent(
+ // code instead of pretending setup succeeded.
+ if (project.kind !== 'none') {
+ const linking = project.kind === 'link'
+ const retry = linking ? `insta project link ${project.id}` : `insta project create${project.name ? ` ${project.name}` : ''}`
if (!loggedIn) {
- info(` not logged in — project not linked; run \`insta login\`, then \`insta project link ${opts.project}\``)
</file context>
| const retry = linking ? `insta project link ${project.id}` : `insta project create${project.name ? ` ${project.name}` : ''}` | |
| const retry = linking ? `insta project link ${project.id}` : `insta project create${project.name ? ` ${project.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40)}` : ''}` |
There was a problem hiding this comment.
Fixed in eae0de0 — the hint now prints slugifyName(project.name), so --create "My App" says insta project create my-app: the name setup itself would have made, and one that survives a paste.
| stdoutTty: !!process.stdout.isTTY, | ||
| }, | ||
| link: (id: string) => Promise<void> = projectLink, | ||
| create: (name?: string) => Promise<void> = (n) => projectCreate(n, {}), |
There was a problem hiding this comment.
P2: On native Windows, bare --create provisions a name derived from the entire drive path rather than this directory because projectCreate receives undefined. Make the default-name resolution platform-safe before using this callback.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/commands/setup.ts, line 400:
<comment>On native Windows, bare `--create` provisions a name derived from the entire drive path rather than this directory because `projectCreate` receives `undefined`. Make the default-name resolution platform-safe before using this callback.</comment>
<file context>
@@ -384,10 +397,13 @@ export async function setupAgent(
stdoutTty: !!process.stdout.isTTY,
},
link: (id: string) => Promise<void> = projectLink,
+ create: (name?: string) => Promise<void> = (n) => projectCreate(n, {}),
): Promise<void> {
if (!opts.yes && !process.stdout.isTTY) {
</file context>
There was a problem hiding this comment.
Declining — pre-existing, and unchanged in kind by this PR.
resolveProjectName splits on / only on origin/main too: git show origin/main:src/commands/project.ts line 51 is cwd.split('/').filter(Boolean).pop(), and every case in test/create-name.test.ts uses a POSIX path. So insta project create with no argument already produces c-users-me-my-app on native Windows; --create reaches the same resolver with the same result.
What this PR changes is the number of entry points, not the outcome or its severity. Fixing it means changing resolveProjectName for insta project create as well — including how GENERIC_DIRS and the home-dir comparison behave on Windows paths — which is a wider change than this PR should carry silently.
`--create "My App"` printed `insta project create My App`; commander drops the excess arg on paste, so it would have made `my` rather than the `my-app` setup itself would have made. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Round 1 — one fix, one decline. Fixed (Wang Miao, cubic 469): the retry hint now prints Declined (Yang Dong, cubic 400) — Windows basename in
The finding is real about the CLI; it is not about this diff. What the PR adds is another entry point to it, not a worse outcome or a new one. And fixing it properly means changing Since it is the blocking finding: if you'd rather this PR carry the resolver fix anyway, say so and I'll add it with the Windows cases in Checks: |
There was a problem hiding this comment.
1 issue found across 3 files
You’re at about 93% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/commands/setup.ts">
<violation number="1" location="src/commands/setup.ts:477">
P2: When `projectCreate` reaches the API but local link persistence fails, this catch treats the project as uncreated and advises rerunning create. Report the existing project and recovery path instead, because the retry can fail with name-taken while the first project remains unlinked.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| try { | ||
| await link(opts.project) | ||
| if (project.kind === 'link') await link(project.id) | ||
| else await create(project.name) |
There was a problem hiding this comment.
P2: When projectCreate reaches the API but local link persistence fails, this catch treats the project as uncreated and advises rerunning create. Report the existing project and recovery path instead, because the retry can fail with name-taken while the first project remains unlinked.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/commands/setup.ts, line 477:
<comment>When `projectCreate` reaches the API but local link persistence fails, this catch treats the project as uncreated and advises rerunning create. Report the existing project and recovery path instead, because the retry can fail with name-taken while the first project remains unlinked.</comment>
<file context>
@@ -441,24 +457,29 @@ export async function setupAgent(
try {
- await link(opts.project)
+ if (project.kind === 'link') await link(project.id)
+ else await create(project.name)
} catch (e) {
// Stop here — like the skill-install failure above, finishing with the success summary
</file context>
Splitting the shared project block leaves the link arm's guard green while the create arm walks into the success summary. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
npm version resyncs package-lock.json alongside package.json. Carries `setup agent --create [name]` (#159). Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
insta setup agent --project <id>binds the working directory to an existing project. A fresh machine with no project yet still needed a second command, so this adds the other half.--create [name]Runs the same
insta project createpath in-process, after the same interactive login offer--projectflows through. Behaviour is deliberately identical to--projecton every edge:--project <id>--create [name]-y, non-TTY, declined login)What
planProjectdoes and does not rejectThe only thing rejected up front is
--createtogether with--project— a contradictory request whose verdict cannot change, so it fails while the machine is still untouched (same reasoning as the existing--env/$INSTA_API_URLconflict).Name resolution stays in
projectCreate. An earlier revision of this branch also rejected a generic cwd (~/projects,/tmp) up front, which meant--createpasted in the most likely directory aborted the whole setup with nothing installed — the opposite of both siblings, sinceinsta project createtreats that case as guidance at exit 0 and--projectnever aborts setup at all.Tests
5 added in
test/setup-agent.test.ts. The ordering guard is mutation-verified: movingplanProjectbelowswitchEnv/ensureturns it red.npm run typecheckandnpm test(47 files, 672 tests) pass.Docs: mirrored into
skills/insta/cli-reference.md— InsForge/insta-skills#65.