Compare commits

...
9 Commits
35 changed files with 193 additions and 36 deletions
@@ -141,6 +141,12 @@ EOF
An effort task is the same call with `--projects '[[atribot]]'`, no actor tag, and `--prop git_branch=…` when the work is not on main.
Efforts and tickets are worked from the frontier, not a calendar, so none carries a scheduled date. The plugin stamps `date_scheduled: <today>` on every task it creates, and `tn` cannot suppress or clear it — empty `--scheduled` values are dropped by both `create` and `update`. Strip it right after each create:
```sh
obsidian property:remove name=date_scheduled path='TaskNotes/Dev/atribot/Network isolation/Split obsync into its own container.md'
```
Create in dependency order so a blocker's title exists before the ticket naming it. Flag reference: `tasknotes-cli`.
A second `--blocked-by` does not add a second blocker — the flag is read once, so every blocker goes in one comma-separated value as above. Commas inside `[[...]]` are literal, so titles containing commas are safe.
@@ -274,6 +280,8 @@ tn list --filter 'projects:contains:"[[Network isolation]]" AND dependencies.isB
- **`tn update`** for everything TaskNotes models — status, priority, dates, estimate, tags, contexts, projects, `blockedBy` — and all queries. It goes through the update service, which maintains `date_modified` and recurring-instance bookkeeping.
- **`obsidian`** for what `tn update` has no flag for: custom properties after creation (`agent_session`, `git_branch`, `git_worktree`) and body text. `obsidian property:set` writes YAML directly and skips the update service.
In a sandboxed session, invoke `obsidian` only as a simple or `&&`-chained command. The command is the Obsidian AppImage, and the sandbox denies its FUSE mount when the call sits inside a shell loop — every iteration fails with `fuse: device not found`. Write a batch over many notes as chained single calls, not a loop.
Mid-flight dependency edits are rare, since blockers are written at creation. When one is needed:
```sh

Before

Width:  |  Height:  |  Size: 420 B

After

Width:  |  Height:  |  Size: 420 B

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

-8
View File
@@ -1,8 +0,0 @@
#/bin/bash
podman run --rm -i \
-v ${HOME}/Music:/downloads:z \
--userns keep-id:uid=1000,gid=1000 \
--entrypoint scdl \
yt-dlp:latest $@
+8 -2
View File
@@ -64,7 +64,11 @@ fi
alias tmux="tmux -2"
alias grep="grep --color=auto"
if command -v brew >/dev/null 2>&1; then
eval "$(brew shellenv)"
elif [[ -x /home/linuxbrew/.linuxbrew/bin/brew ]]; then
eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
fi
# Fix gpg signing
# https://github.com/keybase/keybase-issues/issues/2798
@@ -78,8 +82,8 @@ export TERM="xterm-256color"
export EDITOR=$(which nvim)
# Adding applications to path
if [[ -d ${HOME}/.dotfiles/bin/linux ]]; then
export PATH=$PATH:${HOME}/.dotfiles/bin/linux
if [[ -d ${HOME}/.local/bin ]] && [[ ":${PATH}:" != *":${HOME}/.local/bin:"* ]]; then
export PATH=${HOME}/.local/bin:${PATH}
fi
if [[ -d ${HOME}/bin ]]; then
export PATH=$PATH:${HOME}/bin
@@ -88,7 +92,9 @@ fi
# Enable atuin
# https://docs.atuin.sh
if command -v atuin >/dev/null 2>&1; then
eval "$(atuin init bash)"
fi
# fzf
#eval "$(fzf --bash)"
+50 -13
View File
@@ -33,7 +33,31 @@
],
"defaultMode": "auto"
},
"model": "opus[1m]",
"disableClaudeAiConnectors": true,
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "atuin hook claude-code"
}
]
}
],
"PostToolUseFailure": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "atuin hook claude-code"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
@@ -47,22 +71,33 @@
"command": "~/.claude/hooks/block-file-deletion.sh"
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "atuin hook claude-code"
}
]
}
]
},
"disableWorkflows": true,
"disableArtifact": true,
"statusLine": {
"type": "command",
"command": "bash /home/tgrosinger/.claude/statusline-command.sh"
},
"enabledPlugins": {
"pr-review-toolkit@claude-plugins-official": true,
"gopls-lsp@claude-plugins-official": true,
"frontend-design@claude-plugins-official": true,
"code-simplifier@claude-plugins-official": true,
"skill-creator@claude-plugins-official": true,
"typescript-lsp@claude-plugins-official": true,
"claude-md-management@claude-plugins-official": true,
"security-guidance@claude-plugins-official": true
"code-simplifier@claude-plugins-official": true,
"frontend-design@claude-plugins-official": true,
"gopls-lsp@claude-plugins-official": true,
"pr-review-toolkit@claude-plugins-official": true,
"security-guidance@claude-plugins-official": true,
"skill-creator@claude-plugins-official": true,
"typescript-lsp@claude-plugins-official": true
},
"sandbox": {
"enabled": true,
@@ -86,19 +121,23 @@
"filesystem": {
"allowWrite": [
"~/.local/share/pnpm",
"~/.cache/pnpm"
"~/.cache/pnpm",
"~/Documents/Atrium"
],
"denyRead": [
"~/.ssh",
"~/.config/Signal",
"~/Documents"
],
"allowRead": [
"~/Documents/Atrium"
]
},
"excludedCommands": [
"git push *",
"brew *",
"devbox add *",
"nix *"
"tn *",
"obsidian *"
]
},
"spinnerVerbs": {
@@ -118,9 +157,7 @@
"autoDreamEnabled": true,
"theme": "light",
"editorMode": "vim",
"remoteControlAtStartup": false,
"agentPushNotifEnabled": true,
"disableClaudeAiConnectors": true,
"disableWorkflows": true,
"disableArtifact": true,
"skipAutoPermissionPrompt": true
}
+4 -5
View File
@@ -1,6 +1,6 @@
# Mise shims so non-interactive shells (scripts, editor spawns) find tools.
# Interactive sessions get exact versions from `mise activate` below.
fish_add_path ~/.local/share/mise/shims
fish_add_path --global ~/.local/bin ~/.local/share/mise/shims ~/go/bin
if status is-interactive
# Commands to run in interactive sessions can go here
@@ -9,7 +9,9 @@ if status is-interactive
# https://fishshell.com/docs/current/cmds/fish_greeting.html
set -g fish_greeting
if test -f /home/linuxbrew/.linuxbrew/bin/brew
if command -q brew
brew shellenv | source
else if test -x /home/linuxbrew/.linuxbrew/bin/brew
/home/linuxbrew/.linuxbrew/bin/brew shellenv | source
end
if command -q atuin
@@ -35,9 +37,6 @@ if status is-interactive
# https://fishshell.com/docs/current/cmds/abbr.html
abbr --add --position command pc process-compose
# Set Path
fish_add_path -p /home/tgrosinger/.dotfiles/bin/linux
# Per-directory toolchains and global tools.
# https://mise.jdx.dev/getting-started.html
if type -q mise
-1
View File
@@ -1,4 +1,3 @@
# This file contains fish universal variable definitions.
# VERSION: 3.0
SETUVAR __fish_initialized:4300
SETUVAR fish_user_paths:/home/tgrosinger/\x2elocal/bin\x1e/home/tgrosinger/\x2elocal/share/mise/shims\x1e/home/tgrosinger/go/bin\x1e/home/tgrosinger/\x2edotfiles/bin/linux
+4 -4
View File
@@ -36,10 +36,10 @@
sync = "!git fetch -p && git rebase origin/$(git default-branch)"
[core]
editor = /home/linuxbrew/.linuxbrew/bin/nvim
editor = nvim
pager = delta
attributesfile = /home/tgrosinger/.gitattributes
excludesfile = /home/tgrosinger/.gitignore_global
attributesfile = ~/.gitattributes
excludesfile = ~/.gitignore_global
[help]
autocorrect = prompt
@@ -58,7 +58,7 @@
renames = true
[include]
path = /home/tgrosinger/.config/delta/themes/catppuccin.gitconfig
path = ~/.config/delta/themes/catppuccin.gitconfig
[delta]
# Does not behave well for comments
+11
View File
@@ -0,0 +1,11 @@
.github/instructions
.plans
.scratch
.magpie
**/.claude/settings.local.json
**/.claude/.cc-writes/
CLAUDE.local.md
.worktree-setup.sh
@@ -3,5 +3,5 @@
podman run --rm \
-v ${HOME}/Videos/youtube:/downloads:z \
--userns keep-id:uid=1000,gid=1000 \
yt-dlp:latest -S 'res:1080' $@
yt-dlp:latest -S 'res:1080' --extractor-args "youtube:player-client=web_embedded" $@
+105
View File
@@ -0,0 +1,105 @@
/**
* Atuin extension for pi.
*
* Tracks bash commands executed by pi in Atuin history with author `pi`.
*
* Install with:
* atuin hook install pi
*
* Then restart pi or run /reload.
*/
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
const ATUIN_AUTHOR = "pi";
const ATUIN_TIMEOUT_MS = 10_000;
async function startHistory(
pi: ExtensionAPI,
cwd: string,
command: string,
): Promise<string | undefined> {
try {
const result = await pi.exec(
"atuin",
["history", "start", "--author", ATUIN_AUTHOR, "--", command],
{ cwd, timeout: ATUIN_TIMEOUT_MS },
);
if (result.code !== 0) return undefined;
const id = result.stdout.trim();
return id.length > 0 ? id : undefined;
} catch {
return undefined;
}
}
async function endHistory(
pi: ExtensionAPI,
cwd: string,
historyId: string,
exitCode: number,
): Promise<void> {
try {
await pi.exec(
"atuin",
["history", "end", historyId, "--exit", String(exitCode)],
{ cwd, timeout: ATUIN_TIMEOUT_MS },
);
} catch {
// Ignore Atuin failures so command execution is never blocked.
}
}
// The bash tool reports failures by appending a status line to the result
// text rather than exposing a numeric exit code, so recover it from there.
function exitCodeFromResult(result: unknown, isError: boolean): number {
if (!isError) return 0;
const content = (result as { content?: unknown } | undefined)?.content;
const text = Array.isArray(content)
? content
.map((part) => {
const t = (part as { text?: unknown } | undefined)?.text;
return typeof t === "string" ? t : "";
})
.join("\n")
: "";
const exited = text.match(/Command exited with code (\d+)\s*$/);
if (exited) return Number(exited[1]);
if (/Command aborted\s*$/.test(text)) return 130;
if (/Command timed out after \S+ seconds\s*$/.test(text)) return 124;
return 1;
}
export default function atuinPiExtension(pi: ExtensionAPI) {
// Atuin history IDs for in-flight bash tool calls, keyed by tool call ID.
const pending = new Map<string, string>();
// Observe bash executions through events instead of registering a bash
// tool: registering one conflicts with other extensions that provide
// their own bash tool (sandboxes, RTK, remote runners), while events
// fire no matter which extension's bash tool ends up executing the
// command.
pi.on("tool_call", async (event, ctx: ExtensionContext) => {
if (event.toolName !== "bash") return;
const command = (event.input as { command?: unknown }).command;
if (typeof command !== "string" || command.length === 0) return;
const historyId = await startHistory(pi, ctx.cwd, command);
if (historyId) pending.set(event.toolCallId, historyId);
});
// tool_execution_end also fires when another extension blocks the call,
// unlike tool_result, so entries started above are always closed.
pi.on("tool_execution_end", async (event, ctx: ExtensionContext) => {
const historyId = pending.get(event.toolCallId);
if (!historyId) return;
pending.delete(event.toolCallId);
await endHistory(pi, ctx.cwd, historyId, exitCodeFromResult(event.result, event.isError));
});
}