Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions .github/workflows/release-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,89 @@ jobs:
tdn-cli/dist/tdn-${{ matrix.target }}.zip.sha256
if-no-files-found: error

publish-npm:
name: Publish to npm
needs: build
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 24.x
# Do NOT set registry-url — it breaks OIDC trusted publishing

Comment on lines +107 to +111
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

npm OIDC trusted publishing GitHub Actions setup 2025

💡 Result:

To set up npm trusted publishing using OIDC with GitHub Actions (generally available since July 2025), follow these steps: 1. Ensure prerequisites: - npm CLI v11.5.1 or later (ideally latest, e.g., npm@latest). - Package must already exist on npmjs.com (publish initially with a token if needed). - GitHub-hosted runners (self-hosted not supported). - Node.js 20+ (preferably 24+). 2. Configure trusted publisher on npmjs.com: - Go to https://www.npmjs.com/package/YOUR_PACKAGE/settings (or /access tab). - In "Trusted Publisher" section, select GitHub Actions. - Enter: - Owner: GitHub username or organization (case-sensitive). - Repository: repo name (without owner, case-sensitive). - Workflow filename: e.g., publish.yml (full name with .yml/.yaml extension, must be in .github/workflows/). - Environment: Optional (e.g., 'npm' or 'production'; match workflow if used). - Save. Each package supports only one trusted publisher (editable). 3. Update your GitHub Actions workflow (.github/workflows/publish.yml example): name: Publish to npm on: push: tags: ['v*'] # Or release: types: [created] permissions: id-token: write # Required for OIDC contents: read jobs: publish: runs-on: ubuntu-latest environment: npm # Optional, match npm config steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '24' registry-url: 'https://registry.npmjs.org' - run: npm ci # Use read-only token if needed for deps - run: npm install -g npm@latest # Ensure latest npm - run: npm run build # If needed - run: npm publish --provenance # Auto-generates provenance attestation Key notes: - No NPM_TOKEN secret needed; OIDC handles auth automatically. - id-token: write permission is critical for GitHub to issue OIDC token. - Workflow filename, repo, owner must match exactly npm config. - Provenance is automatic but add --provenance flag explicitly if issues. - Add "repository" field to package.json for verification. - CLI alternative: npm trust github YOUR_PACKAGE --file publish.yml --repo owner/repo --env npm. - Works with monorepos/tools like Lerna/Changesets (Lerna 7+ native support). This eliminates long-lived tokens, uses short-lived workflow-specific creds, and adds supply chain security via provenance.

Citations:


🏁 Script executed:

cat .github/workflows/release-cli.yml

Repository: dannysmith/taskdn

Length of output: 7039


Set registry-url for npm OIDC trusted publishing and add --provenance flag.

The embedded comment "Do NOT set registry-url — it breaks OIDC trusted publishing" is incorrect. According to npm's official trusted publishing documentation (GA since July 2025), registry-url: 'https://registry.npmjs.org' is required in setup-node for OIDC-based publishing to work. Without it, npm publish will fail with a 401/403 error even though id-token: write permission is correctly set.

Additionally, add the --provenance flag to the npm publish commands to generate supply-chain provenance attestations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release-cli.yml around lines 107 - 111, Update the
workflow to set registry-url in the actions/setup-node@v4 step (add
registry-url: 'https://registry.npmjs.org' alongside node-version: 24.x and
remove or correct the "Do NOT set registry-url" comment) and update any npm
publish invocations to include the --provenance flag (e.g., npm publish --access
public --provenance) so OIDC trusted publishing works and provenance
attestations are generated; keep id-token: write permissions as-is for the OIDC
token step.

- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true

- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF#refs/tags/tdn-cli-v}" >> "$GITHUB_OUTPUT"

- name: Publish platform packages
run: |
VERSION="${{ steps.version.outputs.version }}"

publish_unix() {
local target="$1"
echo "Publishing @taskdn/cli-${target}@${VERSION}..."
tar -xzf "../artifacts/tdn-${target}.tar.gz" -C "npm/cli-${target}/"
chmod +x "npm/cli-${target}/tdn"
node -e "
const fs = require('fs');
const p = JSON.parse(fs.readFileSync('npm/cli-${target}/package.json'));
p.version = '${VERSION}';
fs.writeFileSync('npm/cli-${target}/package.json', JSON.stringify(p, null, 2) + '\n');
"
cd "npm/cli-${target}"
npm publish --access public --provenance
cd ../..
}

publish_windows() {
local artifact_target="$1"
local npm_target="$2"
echo "Publishing @taskdn/cli-${npm_target}@${VERSION}..."
unzip -o "../artifacts/tdn-${artifact_target}.zip" -d "npm/cli-${npm_target}/"
node -e "
const fs = require('fs');
const p = JSON.parse(fs.readFileSync('npm/cli-${npm_target}/package.json'));
p.version = '${VERSION}';
fs.writeFileSync('npm/cli-${npm_target}/package.json', JSON.stringify(p, null, 2) + '\n');
"
cd "npm/cli-${npm_target}"
npm publish --access public --provenance
cd ../..
}

publish_unix darwin-arm64
publish_unix darwin-x64
publish_unix linux-arm64
publish_unix linux-x64
publish_windows windows-x64 win32-x64

- name: Publish main wrapper package
run: |
VERSION="${{ steps.version.outputs.version }}"
echo "Publishing @taskdn/cli@${VERSION}..."
node -e "
const fs = require('fs');
const p = JSON.parse(fs.readFileSync('npm/cli/package.json'));
p.version = '${VERSION}';
for (const dep of Object.keys(p.optionalDependencies || {})) {
p.optionalDependencies[dep] = '${VERSION}';
}
fs.writeFileSync('npm/cli/package.json', JSON.stringify(p, null, 2) + '\n');
"
cd npm/cli
npm publish --access public --provenance

release:
name: Create GitHub Release
needs: build
Expand Down
30 changes: 22 additions & 8 deletions docs/tasks-todo/task-x-claude-cowork-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,21 @@ Proof of concept tested on Linux ARM64 (2026-03-14):

### Revised Approach: Install at Session Start

Instead of bundling, have Claude install `tdn` from GitHub Releases at the start of each Cowork session. The user mounts their vault folders, and the skill guides Claude through setup.
Instead of bundling, have Claude install `tdn` at the start of each Cowork session. The user mounts their vault folders, and the skill guides Claude through setup.

**Primary method: npm** (preferred since `registry.npmjs.org` is on Cowork's default allowlist):
```bash
npm install -g @taskdn/cli
```

**Fallback: GitHub Releases** (if npm is unavailable):
```bash
curl -fsSL https://github.com/dannysmith/taskdn/releases/latest/download/install.sh | bash
```

**Why this works:**
- `curl` and `tar` are available in the Cowork VM
- npm's registry is on the Cowork allowlist (GitHub release assets are blocked)
- `curl` and `tar` are available as a fallback
- The install script already supports `linux-arm64`
- Local `.taskdn.json` in the working directory takes precedence over global config
- No changes needed to the binary or release pipeline
Expand Down Expand Up @@ -103,13 +114,16 @@ Instead of bundling, have Claude install `tdn` from GitHub Releases at the start
2. Claude checks: which tdn
├─ Found → normal flow (Claude Code)
└─ Not found → Cowork setup:
a. Install: curl -fsSL <install-url> | bash
a. Try npm: npm install -g @taskdn/cli
├─ Success → continue
└─ Failure → try curl
b. Try curl: curl -fsSL <install-url> | bash
├─ Success → continue
└─ Failure → look for binary in mounted dirs
b. Discover vault dirs in mounted paths
c. Create .taskdn.json with discovered paths
d. Verify: tdn config --ai
e. Continue with normal priming
c. Discover vault dirs in mounted paths
d. Create .taskdn.json with discovered paths
e. Verify: tdn config --ai
f. Continue with normal priming
```

## Path Mapping Reference
Expand Down Expand Up @@ -149,5 +163,5 @@ Just mount directories and work with files directly. Acceptable as degraded fall

## Open Questions

1. **Internet in Cowork VM** — Can the VM always reach GitHub to download releases? If not, the "pre-download binary" fallback becomes the primary path.
1. ~~**Internet in Cowork VM** — Can the VM always reach GitHub to download releases?~~ **Resolved:** The Cowork proxy blocks `release-assets.githubusercontent.com` but allows `registry.npmjs.org`. npm install is now the primary method; curl install script is the fallback for environments where npm isn't available.
2. **Install location persistence** — Does `~/.local/bin/` persist across Cowork sessions or is it wiped? If wiped, install runs every session (acceptable — takes seconds).
127 changes: 36 additions & 91 deletions docs/tasks-todo/task-x-npm-distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,122 +85,67 @@ Instead of storing an `NPM_TOKEN` secret, configure each package on npmjs.com to

## Implementation

### Phase 1: npm Org & Package Setup (manual)
### Phase 1: npm Org & Package Setup (manual)

- [ ] Create the `@taskdn` org on npmjs.com
- [ ] Delete (or deprecate) the old `taskdn-sdk` package
- [ ] Create placeholder packages to reserve the names — each needs an initial publish before trusted publishing can be configured:
- [x] Create the `@taskdn` org on npmjs.com
- [x] Delete (or deprecate) the old `taskdn-sdk` package
- [x] Create placeholder packages (v0.0.0) to reserve the names:
- `@taskdn/cli`
- `@taskdn/cli-darwin-arm64`
- `@taskdn/cli-darwin-x64`
- `@taskdn/cli-linux-arm64`
- `@taskdn/cli-linux-x64`
- `@taskdn/cli-win32-x64`
- [ ] Configure trusted publishing on each package:
- [x] Configure trusted publishing on each package:
- Repository owner: `dannysmith`
- Repository: `taskdn`
- Workflow: `release-cli.yml`

### Phase 2: Create npm Package Files
### Phase 2: Create npm Package Files

Create the `tdn-cli/npm/` directory structure.
Created `tdn-cli/npm/` directory structure with:

**For each platform package** (`cli-darwin-arm64`, etc.):
- [x] 5 platform `package.json` files with `os`/`cpu`/`preferUnplugged` fields
- [x] Main `@taskdn/cli` `package.json` with `bin`, `optionalDependencies`, `engines`
- [x] `bin/tdn` wrapper script using `spawnSync` with passthrough stdio

- [ ] Create `package.json` with:
- `name`, `version` (matching CLI version)
- `os` and `cpu` fields
- `description`, `license`, `repository`
- `preferUnplugged: true` (for Yarn PnP compatibility)
- No `bin` field (main package handles that)
- [ ] The binary itself is NOT committed — CI copies it in at build time
### Phase 3: Update Release Workflow ✅

**For the main package** (`cli/`):
- [x] Added `publish-npm` job to `release-cli.yml` (parallel with `release`, both need `build`)
- [x] OIDC trusted publishing via `permissions.id-token: write`
- [x] `actions/setup-node@v4` with `node-version: 24.x` (no `registry-url`)
- [x] Extracts binaries from build artifacts, sets versions, publishes platform packages then main wrapper

- [ ] Create `package.json` with:
- `name: "@taskdn/cli"`
- `bin: { "tdn": "bin/tdn" }`
- `optionalDependencies` listing all 5 platform packages at the same version
- `engines: { "node": ">=18" }`
- [ ] Create `bin/tdn` wrapper script (Node.js, ~50 lines):
- Platform/arch detection via `process.platform` and `process.arch`
- Map to package name
- `require.resolve()` to find the binary
- `execFileSync()` to run it with passthrough stdio
- Clear error message if no matching platform package is installed
### Phase 4: Update prepare-release Script ✅

### Phase 3: Update Release Workflow
- [x] `prepare-release.js` now bumps versions in all `npm/*/package.json` files
- [x] Added npm publish to post-push output messages

Extend `.github/workflows/release-cli.yml` with a new `publish-npm` job. The workflow structure becomes:
### Phase 5: Fix Existing URL Issues ✅

```
build (existing matrix, unchanged)
├── publish-npm (new, needs: build) ← publishes all 6 npm packages
└── release (existing, needs: build) ← GitHub Release + Homebrew
```

`publish-npm` and `release` run in parallel — both only need `build` to complete.

**New `publish-npm` job:**

- [ ] Add job with `needs: build`, `runs-on: ubuntu-latest`
- [ ] Set `permissions.id-token: write` (required for trusted publishing OIDC)
- [ ] Use `actions/setup-node@v4` with `node-version: 24.x` (do NOT set `registry-url` — it breaks OIDC)
- [ ] Download all build artifacts
- [ ] Extract version from git tag
- [ ] For each platform package:
- Copy the binary from the artifact into `npm/cli-<platform>/`
- Set the version in its `package.json`
- Run `npm publish --access public`
- [ ] After all platform packages are published:
- Set the version + `optionalDependencies` versions in `npm/cli/package.json`
- Run `npm publish --access public` for the main `@taskdn/cli` package
- [ ] Ensure the existing `release` job (GitHub Release + Homebrew) is unaffected

### Phase 4: Update prepare-release Script

- [ ] Update `tdn-cli/scripts/prepare-release.js` to also bump versions in all `npm/*/package.json` files
- [ ] Fix repo URL: `taskdn/taskdn` → `dannysmith/taskdn` in the script's output messages

### Phase 5: Fix Existing URL Issues

These are wrong in multiple places and should be fixed regardless of npm publishing.

- [ ] `tdn-cli/scripts/install.sh` line 16: `REPO="taskdn/taskdn"` → `REPO="dannysmith/taskdn"`
- [ ] `tdn-claude-plugin/commands/prime.md` line 13: fix GitHub URL to `dannysmith/taskdn`
- [ ] `tdn-claude-plugin/skills/task-management/cowork.md` line 28: fix GitHub URL
- [ ] `tdn-cli/scripts/prepare-release.js` lines 132-136: fix GitHub URLs

### Phase 6: Update Claude Plugin for npm Install

Update the plugin's install flow to prefer npm when available.
- [x] `tdn-cli/scripts/install.sh`: `REPO="dannysmith/taskdn"`
- [x] `tdn-claude-plugin/commands/prime.md`: fixed GitHub URL
- [x] `tdn-claude-plugin/skills/task-management/cowork.md`: fixed GitHub URL
- [x] `tdn-cli/scripts/prepare-release.js`: fixed GitHub URLs

- [ ] Update `tdn-claude-plugin/commands/prime.md`:
- Step 3a: try `npm install -g @taskdn/cli` first
- Step 3b: fall back to curl install script (with corrected URL)
- Step 3c: search mounted dirs for binary
- Step 3d: fall back to direct file access
- [ ] Update `tdn-claude-plugin/skills/task-management/cowork.md`:
- Primary method: `npm install -g @taskdn/cli`
- Fallback: curl install script
- Fallback: pre-placed binary in mounted folder
- Fallback: direct file access (degraded mode)
- [ ] Update `docs/tasks-todo/task-x-claude-cowork-integration.md` to reflect npm as the primary Cowork install method
### Phase 6: Update Claude Plugin for npm Install ✅

### Phase 7: Dual-Install Conflict Detection
- [x] Updated `tdn-claude-plugin/commands/prime.md`: npm first, curl fallback, mounted binary fallback, direct file access fallback
- [x] Updated `tdn-claude-plugin/skills/task-management/cowork.md`: npm as primary install method with curl and mounted binary fallbacks
- [x] Updated `docs/tasks-todo/task-x-claude-cowork-integration.md`: npm as primary method, resolved open question about Cowork proxy

Users might have `tdn` installed via both Homebrew and npm, which can cause confusion about which binary is being used.
### Phase 7: Dual-Install Conflict Detection ✅

- [ ] Research how other CLI tools handle this (e.g. how `biome`, `eslint`, `prettier` handle Homebrew vs npm coexistence)
- [ ] Add a check in the npm wrapper script (`bin/tdn`): if `tdn` exists at a Homebrew path (e.g. from `brew --prefix`), print a warning explaining the situation and which binary is being used
- [ ] Consider whether the Homebrew formula should do the inverse check — likely not worth it since Homebrew formulas can't easily detect npm globals, and the npm wrapper is the more common entry point
- [x] Most tools (biome, esbuild, etc.) don't cross-check — PATH order determines which runs
- [x] Added macOS-only check in `bin/tdn`: warns if Homebrew `tdn` exists at `/opt/homebrew/bin/tdn` or `/usr/local/bin/tdn`, suppressible via `TDN_NO_DUAL_WARN=1`
- [x] Homebrew formula does not need an inverse check (can't easily detect npm globals)

### Phase 8: Update Documentation
### Phase 8: Update Documentation

- [ ] Update `tdn-cli/README.md`: add npm as an installation method alongside Homebrew and the install script
- [ ] Update `tdn-cli/docs/developer/releases.md`: document the npm publishing step in the release process
- [ ] Update `website/` user-facing docs: add npm installation instructions to the CLI installation page
- [ ] Update `tdn-claude-plugin/README.md`: mention npm install as the primary method for Cowork environments
- [x] `tdn-cli/README.md`: added npm as first installation method
- [x] `tdn-cli/docs/developer/releases.md`: documented npm publishing step, prerequisites, troubleshooting
- [x] `website/src/content/docs/cli/overview.mdx`: already had npm tab (no change needed)
- [x] `tdn-claude-plugin/README.md`: updated Cowork section to list npm as primary install method

## Testing

Expand Down
2 changes: 1 addition & 1 deletion tdn-claude-plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tdn",
"version": "0.1.1",
"version": "0.1.2",
"description": "Task and project management with the tdn CLI. Helps Claude work as a productivity assistant, managing tasks, projects, and areas stored as markdown files on disk.",
"author": {
"name": "Danny Smith"
Expand Down
15 changes: 8 additions & 7 deletions tdn-claude-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,16 +115,17 @@ You must **share/mount your vault folders** with the Cowork session:
When you run `/tdn:prime` (or ask about your tasks), Claude will:

1. Detect that `tdn` isn't installed
2. Download and install it automatically from GitHub Releases
3. Discover your mounted vault directories
4. Create a local config file pointing to them
5. Proceed normally
2. Install it via `npm install -g @taskdn/cli` (preferred — npm registry is on the Cowork allowlist)
3. If npm fails, fall back to downloading from GitHub Releases
4. Discover your mounted vault directories
5. Create a local config file pointing to them
6. Proceed normally

If the download fails (e.g. no internet), Claude will look for a `tdn` binary you've placed in one of your mounted folders. If no binary is available at all, Claude falls back to working with your task files directly.
If both install methods fail, Claude will look for a `tdn` binary you've placed in one of your mounted folders. If no binary is available at all, Claude falls back to working with your task files directly.

### Pre-downloading the Binary (Optional)
### Pre-placing the Binary (Optional)

If your Cowork sessions don't have internet access, download the Linux ARM64 binary from [GitHub Releases](https://github.com/taskdn/taskdn/releases) and place it in one of the folders you share with Cowork.
If neither npm nor the install script works, download the Linux ARM64 binary from [GitHub Releases](https://github.com/dannysmith/taskdn/releases) and place it in one of the folders you share with Cowork.

### Notes

Expand Down
15 changes: 8 additions & 7 deletions tdn-claude-plugin/commands/prime.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: Prime the current session with general context from taskdn and load the task-management skill
allowed-tools: Bash(tdn:*), Bash(which:*), Bash(curl:*), Bash(export:*), Bash(find:*), Bash(ls:*), Bash(cat:*), Bash(echo:*), Skill(tdn:task-management)
allowed-tools: Bash(tdn:*), Bash(which:*), Bash(npm:*), Bash(curl:*), Bash(export:*), Bash(find:*), Bash(ls:*), Bash(cat:*), Bash(echo:*), Skill(tdn:task-management)
---

This session is being primed with context on the user's personal task management system: tasks, projects and life areas. Do this:
Expand All @@ -10,11 +10,12 @@ This session is being primed with context on the user's personal task management
- **If found** → skip to step 4.
- **If not found** → continue to step 3.
3. Set up tdn for this environment (likely Claude Cowork). See [cowork.md](../skills/task-management/cowork.md) for full details. In short:
a. Install: `curl -fsSL https://github.com/taskdn/taskdn/releases/latest/download/install.sh | bash && export PATH="$PATH:$HOME/.local/bin"`
b. If install fails, search mounted dirs for a `tdn` binary: `find /sessions/*/mnt/ /mnt/ -name 'tdn' -o -name 'tdn-linux-arm64' 2>/dev/null`
c. Discover vault directories in mounted paths (look for `tasks`, `projects`, `areas` folders)
d. Create a `.taskdn.json` in the current directory with the discovered paths
e. Verify with `tdn config --ai`
f. If none of this works, tell the user and fall back to direct file access using the skill's specification and templates docs.
a. Try npm: `npm install -g @taskdn/cli && export PATH="$PATH:$(npm prefix -g)/bin"`
b. If npm fails, try curl: `curl -fsSL https://github.com/dannysmith/taskdn/releases/latest/download/install.sh | bash && export PATH="$PATH:$HOME/.local/bin"`
c. If both fail, search mounted dirs for a `tdn` binary: `find /sessions/*/mnt/ /mnt/ -name 'tdn' -o -name 'tdn-linux-arm64' 2>/dev/null`
d. Discover vault directories in mounted paths (look for `tasks`, `projects`, `areas` folders)
e. Create a `.taskdn.json` in the current directory with the discovered paths
f. Verify with `tdn config --ai`
g. If none of this works, tell the user and fall back to direct file access using the skill's specification and templates docs.
4. Run `tdn config --ai && tdn context --ai` (so output → your context)
5. Report only: "Got it... <x> areas, <y> active projects... 🚀" (where x & y are based on the output of step 4)
17 changes: 13 additions & 4 deletions tdn-claude-plugin/skills/task-management/cowork.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,23 @@ When you detect you're in a Cowork environment (no `tdn` in PATH, Linux platform

### Step 1: Install the tdn Binary

**Primary method — download from GitHub Releases:**
**Primary method — install via npm:**

```bash
curl -fsSL https://github.com/taskdn/taskdn/releases/latest/download/install.sh | bash
npm install -g @taskdn/cli
export PATH="$PATH:$(npm prefix -g)/bin"
```

This is the most reliable method in Cowork, since `registry.npmjs.org` is on the default allowlist.

**Fallback 1 — download from GitHub Releases:**

```bash
curl -fsSL https://github.com/dannysmith/taskdn/releases/latest/download/install.sh | bash
export PATH="$PATH:$HOME/.local/bin"
```

**Fallback — if download fails (no internet, etc.):**
**Fallback 2 — pre-placed binary in mounted folder:**

Search mounted directories for a pre-placed binary:

Expand All @@ -40,7 +49,7 @@ find /sessions/*/mnt/ -name 'tdn' -o -name 'tdn-linux-arm64' 2>/dev/null

If found, use it directly by its full path.

If neither method works, fall back to direct file access mode (see "Degraded Mode" below).
If no method works, fall back to direct file access mode (see "Degraded Mode" below).

### Step 2: Discover Mounted Vault Directories

Expand Down
Loading
Loading