@aitne-sh/aitne 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 +464 -0
- package/agent-assets/agent-profiles/_safety.md +26 -0
- package/agent-assets/agent-profiles/conversational.md +33 -0
- package/agent-assets/agent-profiles/docs-qa.md +24 -0
- package/agent-assets/agent-profiles/observer.md +28 -0
- package/agent-assets/agent-profiles/profile-importer.md +63 -0
- package/agent-assets/agent-profiles/proxy.md +28 -0
- package/agent-assets/agent-profiles/routine.md +16 -0
- package/agent-assets/agent-profiles/task.md +18 -0
- package/agent-assets/docs/concepts/agent-day.md +88 -0
- package/agent-assets/docs/concepts/auth-health.md +75 -0
- package/agent-assets/docs/concepts/backends-and-tiers.md +126 -0
- package/agent-assets/docs/concepts/costs-and-quotas.md +103 -0
- package/agent-assets/docs/concepts/delegated-mode.md +223 -0
- package/agent-assets/docs/concepts/memory-model.md +118 -0
- package/agent-assets/docs/concepts/observations.md +80 -0
- package/agent-assets/docs/concepts/process-keys.md +89 -0
- package/agent-assets/docs/concepts/routines.md +108 -0
- package/agent-assets/docs/concepts/safety-and-execution.md +109 -0
- package/agent-assets/docs/concepts/safety-model.md +279 -0
- package/agent-assets/docs/concepts/skills.md +100 -0
- package/agent-assets/docs/features/integrations/calendar.md +92 -0
- package/agent-assets/docs/features/integrations/git.md +95 -0
- package/agent-assets/docs/features/integrations/github.md +170 -0
- package/agent-assets/docs/features/integrations/mail.md +106 -0
- package/agent-assets/docs/features/integrations/notion.md +69 -0
- package/agent-assets/docs/features/integrations/obsidian.md +71 -0
- package/agent-assets/docs/features/lifestyle/git.md +178 -0
- package/agent-assets/docs/features/lifestyle/reading.md +93 -0
- package/agent-assets/docs/features/lifestyle/receipts.md +71 -0
- package/agent-assets/docs/features/lifestyle/travel-bookings.md +44 -0
- package/agent-assets/docs/features/lifestyle/travel-time.md +52 -0
- package/agent-assets/docs/features/memory-files/agent-journal.md +105 -0
- package/agent-assets/docs/features/memory-files/projects.md +56 -0
- package/agent-assets/docs/features/memory-files/roadmap.md +61 -0
- package/agent-assets/docs/features/memory-files/schedule.md +112 -0
- package/agent-assets/docs/features/memory-files/today.md +73 -0
- package/agent-assets/docs/features/memory-files/user-profile.md +81 -0
- package/agent-assets/docs/features/messaging/dashboard-chat.md +93 -0
- package/agent-assets/docs/features/messaging/discord.md +50 -0
- package/agent-assets/docs/features/messaging/overview.md +111 -0
- package/agent-assets/docs/features/messaging/pairing-and-magic-phrase.md +69 -0
- package/agent-assets/docs/features/messaging/slack.md +51 -0
- package/agent-assets/docs/features/messaging/telegram.md +63 -0
- package/agent-assets/docs/features/messaging/whatsapp.md +48 -0
- package/agent-assets/docs/features/operations/activity-and-conversations.md +105 -0
- package/agent-assets/docs/features/operations/approvals.md +58 -0
- package/agent-assets/docs/features/operations/backend-routing.md +62 -0
- package/agent-assets/docs/features/operations/cost-tracking.md +59 -0
- package/agent-assets/docs/features/operations/notifications.md +69 -0
- package/agent-assets/docs/features/operations/quiet-hours.md +106 -0
- package/agent-assets/docs/features/operations/schedule-approaching.md +60 -0
- package/agent-assets/docs/features/routines/custom-routines.md +101 -0
- package/agent-assets/docs/features/routines/evening-review.md +81 -0
- package/agent-assets/docs/features/routines/hourly-check.md +85 -0
- package/agent-assets/docs/features/routines/monthly-review.md +65 -0
- package/agent-assets/docs/features/routines/morning-routine.md +123 -0
- package/agent-assets/docs/features/routines/weekly-review.md +70 -0
- package/agent-assets/docs/getting-started/01-what-is-this.md +192 -0
- package/agent-assets/docs/getting-started/02-first-steps.md +80 -0
- package/agent-assets/docs/getting-started/03-what-can-this-do.md +110 -0
- package/agent-assets/docs/getting-started/04-first-day.md +287 -0
- package/agent-assets/docs/glossary.md +116 -0
- package/agent-assets/docs/guides/add-a-custom-routine.md +71 -0
- package/agent-assets/docs/guides/backup-and-restore.md +54 -0
- package/agent-assets/docs/guides/change-which-model-handles-x.md +47 -0
- package/agent-assets/docs/guides/connect-a-new-mail-account.md +59 -0
- package/agent-assets/docs/guides/import-knowledge-file.md +275 -0
- package/agent-assets/docs/guides/install-and-run.md +72 -0
- package/agent-assets/docs/guides/migrate-machines.md +52 -0
- package/agent-assets/docs/guides/pause-the-agent.md +65 -0
- package/agent-assets/docs/guides/reinstall-cleanly.md +52 -0
- package/agent-assets/docs/guides/setup-wizard.md +107 -0
- package/agent-assets/docs/guides/switch-default-backend.md +60 -0
- package/agent-assets/docs/reference/api.md +51 -0
- package/agent-assets/docs/reference/cli-commands.md +121 -0
- package/agent-assets/docs/reference/config.md +74 -0
- package/agent-assets/docs/reference/disallowed-tools.md +76 -0
- package/agent-assets/docs/reference/keyboard-shortcuts.md +39 -0
- package/agent-assets/docs/reference/process-keys.md +59 -0
- package/agent-assets/docs/reference/skills.md +50 -0
- package/agent-assets/docs/troubleshooting/auth-failed.md +57 -0
- package/agent-assets/docs/troubleshooting/dashboard-shows-degraded.md +55 -0
- package/agent-assets/docs/troubleshooting/fallback-keeps-firing.md +54 -0
- package/agent-assets/docs/troubleshooting/messaging-not-pairing.md +53 -0
- package/agent-assets/docs/troubleshooting/morning-routine-didnt-run.md +75 -0
- package/agent-assets/docs/troubleshooting/observation-not-detected.md +57 -0
- package/agent-assets/docs/troubleshooting/quota-exhausted.md +57 -0
- package/agent-assets/optimizer-skills/drift-analysis/SKILL.md +75 -0
- package/agent-assets/optimizer-skills/knowledge-map/SKILL.md +71 -0
- package/agent-assets/optimizer-skills/skill-curation/SKILL.md +108 -0
- package/agent-assets/project-doc-templates/git-repo.md +21 -0
- package/agent-assets/project-doc-templates/project.md +38 -0
- package/agent-assets/skills/attach/SKILL.md +104 -0
- package/agent-assets/skills/context/SKILL.md +257 -0
- package/agent-assets/skills/context/curation.json +37 -0
- package/agent-assets/skills/context/seeds/file-responsibilities.seed.json +13 -0
- package/agent-assets/skills/context/seeds/frontmatter-requirements.seed.json +40 -0
- package/agent-assets/skills/docs-search/SKILL.md +176 -0
- package/agent-assets/skills/external-services/SKILL.delegated.claude.md +369 -0
- package/agent-assets/skills/external-services/SKILL.delegated.codex.md +349 -0
- package/agent-assets/skills/external-services/SKILL.delegated.gemini.md +347 -0
- package/agent-assets/skills/external-services/SKILL.md +371 -0
- package/agent-assets/skills/mail/SKILL.delegated.claude.md +284 -0
- package/agent-assets/skills/mail/SKILL.delegated.codex.md +261 -0
- package/agent-assets/skills/mail/SKILL.delegated.gemini.md +255 -0
- package/agent-assets/skills/mail/SKILL.md +313 -0
- package/agent-assets/skills/mail/references/errors.md +17 -0
- package/agent-assets/skills/mail/references/providers.md +40 -0
- package/agent-assets/skills/mail/references/query-grammar.md +24 -0
- package/agent-assets/skills/management-policy/SKILL.md +307 -0
- package/agent-assets/skills/management-policy/curation.json +13 -0
- package/agent-assets/skills/management-policy/seeds/policy-file-shape.seed.json +16 -0
- package/agent-assets/skills/management-task-modify/SKILL.md +202 -0
- package/agent-assets/skills/management-task-register/SKILL.md +330 -0
- package/agent-assets/skills/management-task-stop/SKILL.md +166 -0
- package/agent-assets/skills/notify/SKILL.md +196 -0
- package/agent-assets/skills/notion/SKILL.delegated.claude.md +254 -0
- package/agent-assets/skills/notion/SKILL.delegated.codex.md +195 -0
- package/agent-assets/skills/notion/SKILL.delegated.gemini.md +194 -0
- package/agent-assets/skills/notion/SKILL.md +86 -0
- package/agent-assets/skills/observations/SKILL.md +234 -0
- package/agent-assets/skills/observations/curation.json +13 -0
- package/agent-assets/skills/observations/seeds/source-namespacing.seed.json +20 -0
- package/agent-assets/skills/project-doc/SKILL.md +86 -0
- package/agent-assets/skills/project-doc/curation.json +21 -0
- package/agent-assets/skills/project-doc/seeds/project-shape.seed.json +25 -0
- package/agent-assets/skills/project-doc/seeds/slug-grammar.seed.json +20 -0
- package/agent-assets/skills/reading/SKILL.md +198 -0
- package/agent-assets/skills/reading/references/reading-taste.md +197 -0
- package/agent-assets/skills/receipts/SKILL.md +134 -0
- package/agent-assets/skills/roadmap/SKILL.md +276 -0
- package/agent-assets/skills/roadmap/curation.json +13 -0
- package/agent-assets/skills/roadmap/references/horizon-tags.md +40 -0
- package/agent-assets/skills/roadmap/references/preparation-timeline.md +47 -0
- package/agent-assets/skills/roadmap/seeds/entry-types.seed.json +16 -0
- package/agent-assets/skills/schedule/SKILL.md +228 -0
- package/agent-assets/skills/scheduled-managed-task/SKILL.md +392 -0
- package/agent-assets/skills/today/SKILL.md +198 -0
- package/agent-assets/skills/today/curation.json +21 -0
- package/agent-assets/skills/today/seeds/agent-notes-flavors.seed.json +17 -0
- package/agent-assets/skills/today/seeds/section-shape.seed.json +17 -0
- package/agent-assets/skills/travel/SKILL.md +132 -0
- package/agent-assets/skills/travel-time/SKILL.md +149 -0
- package/agent-assets/skills/user-interview/SKILL.md +323 -0
- package/agent-assets/skills/user-interview/references/sweep-and-fallback.md +94 -0
- package/agent-assets/skills/user-profile/SKILL.md +210 -0
- package/agent-assets/skills/user-profile/curation.json +29 -0
- package/agent-assets/skills/user-profile/seeds/learned-context-format.seed.json +14 -0
- package/agent-assets/skills/user-profile/seeds/routing-table.seed.json +53 -0
- package/agent-assets/skills/user-profile/seeds/topic-files.seed.json +27 -0
- package/agent-assets/task-flows/dashboard.docs_qa.md +43 -0
- package/agent-assets/task-flows/default.md +11 -0
- package/agent-assets/task-flows/git.branch.created.md +25 -0
- package/agent-assets/task-flows/git.lifecycle.poll.md +52 -0
- package/agent-assets/task-flows/git.local_ahead.stale.md +34 -0
- package/agent-assets/task-flows/git.merge_to_default.md +30 -0
- package/agent-assets/task-flows/git.project.refresh_architecture.md +100 -0
- package/agent-assets/task-flows/git.project.retemplate.md +73 -0
- package/agent-assets/task-flows/git.push.detected.md +32 -0
- package/agent-assets/task-flows/git.push.force_pushed.md +36 -0
- package/agent-assets/task-flows/git.tag.created.md +24 -0
- package/agent-assets/task-flows/github.assigned.md +43 -0
- package/agent-assets/task-flows/github.pull_request.review_requested.md +57 -0
- package/agent-assets/task-flows/github.security_alert.md +45 -0
- package/agent-assets/task-flows/github.workflow_run.failed.md +57 -0
- package/agent-assets/task-flows/knowledge.import.md +161 -0
- package/agent-assets/task-flows/message.received.dm.md +142 -0
- package/agent-assets/task-flows/message.received.dm_first.md +117 -0
- package/agent-assets/task-flows/message.received.md +14 -0
- package/agent-assets/task-flows/routine.custom.md +38 -0
- package/agent-assets/task-flows/routine.evening_review.md +323 -0
- package/agent-assets/task-flows/routine.hourly_check.delegated.claude.md +405 -0
- package/agent-assets/task-flows/routine.hourly_check.delegated.codex.md +400 -0
- package/agent-assets/task-flows/routine.hourly_check.delegated.gemini.md +404 -0
- package/agent-assets/task-flows/routine.hourly_check.md +184 -0
- package/agent-assets/task-flows/routine.hourly_check.triage.md +93 -0
- package/agent-assets/task-flows/routine.monthly_review.md +250 -0
- package/agent-assets/task-flows/routine.morning_routine.md +300 -0
- package/agent-assets/task-flows/routine.morning_routine_initial.md +184 -0
- package/agent-assets/task-flows/routine.roadmap_refresh.md +275 -0
- package/agent-assets/task-flows/routine.today_refresh.md +172 -0
- package/agent-assets/task-flows/routine.user_profile_sweep.md +242 -0
- package/agent-assets/task-flows/routine.weekly_review.md +247 -0
- package/agent-assets/task-flows/schedule.approaching.md +124 -0
- package/agent-assets/task-flows/scheduled.dm.md +391 -0
- package/agent-assets/task-flows/scheduled.task.md +141 -0
- package/agent-assets/task-flows/setup.initial.md +277 -0
- package/agent-assets/task-flows/setup.update.md +53 -0
- package/agent-assets/templates/README.md +85 -0
- package/agent-assets/templates/_index.md +39 -0
- package/agent-assets/templates/_manifest.json +103 -0
- package/agent-assets/templates/agent/journal.md +10 -0
- package/agent-assets/templates/agent/profile-questions.md +74 -0
- package/agent-assets/templates/context-index.md +42 -0
- package/agent-assets/templates/dossiers/_index.md +22 -0
- package/agent-assets/templates/dossiers/evening.md +23 -0
- package/agent-assets/templates/dossiers/hourly.md +23 -0
- package/agent-assets/templates/dossiers/monthly.md +23 -0
- package/agent-assets/templates/dossiers/morning.md +23 -0
- package/agent-assets/templates/dossiers/roadmap.md +23 -0
- package/agent-assets/templates/dossiers/weekly.md +23 -0
- package/agent-assets/templates/projects/_active.base +14 -0
- package/agent-assets/templates/projects/_index.md +29 -0
- package/agent-assets/templates/roadmap.md +15 -0
- package/agent-assets/templates/routines/_index.md +20 -0
- package/agent-assets/templates/routines/evening.md +22 -0
- package/agent-assets/templates/routines/hourly.md +30 -0
- package/agent-assets/templates/routines/monthly.md +25 -0
- package/agent-assets/templates/routines/morning.md +26 -0
- package/agent-assets/templates/routines/weekly.md +23 -0
- package/agent-assets/templates/rules/_index.md +19 -0
- package/agent-assets/templates/rules/journal-export.md +41 -0
- package/agent-assets/templates/rules/journal-format.md +61 -0
- package/agent-assets/templates/rules/management.md +48 -0
- package/agent-assets/templates/rules/mcp.md +40 -0
- package/agent-assets/templates/rules/policies/_index.md +22 -0
- package/agent-assets/templates/rules/redaction.md +30 -0
- package/agent-assets/templates/today.md +13 -0
- package/agent-assets/templates/user/_index.md +16 -0
- package/agent-assets/templates/user/expertise.md +7 -0
- package/agent-assets/templates/user/goals.md +7 -0
- package/agent-assets/templates/user/people.md +7 -0
- package/agent-assets/templates/user/personal.md +7 -0
- package/agent-assets/templates/user/profile.md +28 -0
- package/agent-assets/templates/user/work.md +7 -0
- package/bin/aitne.mjs +1096 -0
- package/package.json +78 -0
- package/personal-agent.mjs +39 -0
- package/scripts/browser.mjs +99 -0
- package/scripts/check-redaction-coverage.mjs +109 -0
- package/scripts/commands/audit.mjs +309 -0
- package/scripts/commands/doctor.mjs +437 -0
- package/scripts/commands/open.mjs +40 -0
- package/scripts/commands/setup.mjs +21 -0
- package/scripts/commands/uninstall.mjs +114 -0
- package/scripts/commands/update.mjs +96 -0
- package/scripts/commands/version.mjs +62 -0
- package/scripts/commands.md +0 -0
- package/scripts/lib/sqlite-loader.mjs +49 -0
- package/scripts/message-discipline-digest.mjs +535 -0
- package/scripts/poc/google-connector-inheritance/REPORT.md +197 -0
- package/scripts/poc/google-connector-inheritance/claude-sdk-probe.mjs +79 -0
- package/scripts/remint-roadmap-ids.mjs +257 -0
- package/scripts/rm-paths.mjs +22 -0
- package/scripts/run-node.mjs +223 -0
- package/scripts/smoke-obsidian-api.mjs +166 -0
- package/scripts/start.mjs +160 -0
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aitne-sh/aitne",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Aitne — a local-first, proactive personal AI agent. A long-running TypeScript daemon is the nervous system; Claude Code (or Codex / Gemini CLI) is the brain. All persistent memory lives in local Markdown files.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"agent",
|
|
8
|
+
"personal-assistant",
|
|
9
|
+
"claude",
|
|
10
|
+
"claude-code",
|
|
11
|
+
"codex",
|
|
12
|
+
"gemini",
|
|
13
|
+
"local-first",
|
|
14
|
+
"obsidian",
|
|
15
|
+
"cli",
|
|
16
|
+
"daemon"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://github.com/Aitne-sh/Aitne#readme",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/Aitne-sh/Aitne/issues"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/Aitne-sh/Aitne.git"
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"author": "Aitne contributors",
|
|
28
|
+
"type": "module",
|
|
29
|
+
"bin": {
|
|
30
|
+
"aitne": "./bin/aitne.mjs"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"bin",
|
|
34
|
+
"scripts",
|
|
35
|
+
"personal-agent.mjs",
|
|
36
|
+
"agent-assets",
|
|
37
|
+
"LICENSE",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@aitne/daemon": "0.1.0",
|
|
42
|
+
"@aitne/dashboard": "0.1.0",
|
|
43
|
+
"@aitne/shared": "0.1.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@typescript-eslint/eslint-plugin": "^8.58.1",
|
|
47
|
+
"@typescript-eslint/parser": "^8.58.1",
|
|
48
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
49
|
+
"eslint": "^9.39.4",
|
|
50
|
+
"turbo": "^2.9.5",
|
|
51
|
+
"typescript": "^5.8.0",
|
|
52
|
+
"typescript-eslint": "^8.58.1",
|
|
53
|
+
"vitest": "^3.2.0"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=22.0.0"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "turbo run build",
|
|
63
|
+
"start": "node bin/aitne.mjs start",
|
|
64
|
+
"stop": "node bin/aitne.mjs stop",
|
|
65
|
+
"restart": "node bin/aitne.mjs restart",
|
|
66
|
+
"status": "node bin/aitne.mjs status",
|
|
67
|
+
"logs": "node bin/aitne.mjs logs",
|
|
68
|
+
"dev": "node bin/aitne.mjs dev",
|
|
69
|
+
"doctor": "node bin/aitne.mjs doctor",
|
|
70
|
+
"audit": "node bin/aitne.mjs audit",
|
|
71
|
+
"test": "node scripts/check-redaction-coverage.mjs && vitest run --coverage",
|
|
72
|
+
"test:watch": "vitest",
|
|
73
|
+
"check:redaction": "node scripts/check-redaction-coverage.mjs",
|
|
74
|
+
"lint": "turbo run lint",
|
|
75
|
+
"typecheck:tests": "turbo run typecheck:tests",
|
|
76
|
+
"clean": "turbo run clean && node scripts/rm-paths.mjs node_modules .buildstamp"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import module from "node:module";
|
|
4
|
+
|
|
5
|
+
// Enable Node.js module compile cache for faster startup
|
|
6
|
+
// https://nodejs.org/api/module.html#module-compile-cache
|
|
7
|
+
if (module.enableCompileCache && !process.env.NODE_DISABLE_COMPILE_CACHE) {
|
|
8
|
+
try {
|
|
9
|
+
module.enableCompileCache();
|
|
10
|
+
} catch {
|
|
11
|
+
// Ignore errors
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const isModuleNotFoundError = (err) =>
|
|
16
|
+
err && typeof err === "object" && "code" in err && err.code === "ERR_MODULE_NOT_FOUND";
|
|
17
|
+
|
|
18
|
+
// Resolves to packages/daemon/dist/index.js in workspace dev (pnpm symlinks
|
|
19
|
+
// node_modules/@aitne/daemon → packages/daemon) and to the installed package
|
|
20
|
+
// in a published global install. Falls back to the workspace path when the
|
|
21
|
+
// dependency cannot be resolved (helps in fresh checkouts pre-`pnpm install`).
|
|
22
|
+
try {
|
|
23
|
+
await import("@aitne/daemon");
|
|
24
|
+
} catch (err) {
|
|
25
|
+
if (isModuleNotFoundError(err)) {
|
|
26
|
+
try {
|
|
27
|
+
await import("./packages/daemon/dist/index.js");
|
|
28
|
+
} catch (innerErr) {
|
|
29
|
+
if (isModuleNotFoundError(innerErr)) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"personal-agent: cannot find @aitne/daemon — run `pnpm install` and `pnpm build` first.",
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
throw innerErr;
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shared browser / HTTP-readiness helpers used by cli.mjs (`pa start`) and
|
|
6
|
+
* start.mjs (`pa dev`). Keep this file minimal and free of project state.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Fetch `url` with a hard timeout. Returns true iff *any* HTTP response came
|
|
11
|
+
* back — we treat a 404 or 500 as "the server is reachable, it just doesn't
|
|
12
|
+
* like this request", which is good enough to know Next.js has stopped
|
|
13
|
+
* rejecting connections. Network errors / abort / ECONNREFUSED → false.
|
|
14
|
+
*/
|
|
15
|
+
export async function fetchHttpOk(url, timeoutMs = 2_000) {
|
|
16
|
+
const controller = new AbortController();
|
|
17
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
18
|
+
try {
|
|
19
|
+
await fetch(url, { signal: controller.signal });
|
|
20
|
+
return true;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
} finally {
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Poll `url` until the server answers, a liveness check fails, or we time out.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} url
|
|
32
|
+
* @param {object} [opts]
|
|
33
|
+
* @param {Array<() => boolean>} [opts.liveness] Each predicate returns true
|
|
34
|
+
* while its watched process is still running. Any false short-circuits to
|
|
35
|
+
* `false` so we don't poll a dead server.
|
|
36
|
+
* @param {number} [opts.timeoutMs] Overall budget (default 30s).
|
|
37
|
+
* @param {number} [opts.pollMs] Sleep between attempts (default 300ms).
|
|
38
|
+
* @returns {Promise<boolean>} true if ready, false on timeout / liveness fail.
|
|
39
|
+
*/
|
|
40
|
+
export async function waitForHttpReady(url, opts = {}) {
|
|
41
|
+
const { liveness = [], timeoutMs = 30_000, pollMs = 300 } = opts;
|
|
42
|
+
const deadline = Date.now() + timeoutMs;
|
|
43
|
+
while (Date.now() < deadline) {
|
|
44
|
+
for (const check of liveness) {
|
|
45
|
+
if (!check()) return false;
|
|
46
|
+
}
|
|
47
|
+
const attemptBudget = Math.max(200, Math.min(2_000, deadline - Date.now()));
|
|
48
|
+
if (await fetchHttpOk(url, attemptBudget)) return true;
|
|
49
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Open `url` in the user's default browser. Returns a promise that resolves
|
|
56
|
+
* to true iff the helper binary spawned successfully *and* no error surfaced
|
|
57
|
+
* within ~150ms — enough to catch ENOENT (e.g. `xdg-open` missing on headless
|
|
58
|
+
* Linux). On darwin/win32 the helper itself always spawns, so this is
|
|
59
|
+
* effectively "did we attempt" rather than "did a tab actually open".
|
|
60
|
+
*/
|
|
61
|
+
export function openBrowser(url) {
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
let cmd;
|
|
64
|
+
let args;
|
|
65
|
+
switch (process.platform) {
|
|
66
|
+
case "darwin":
|
|
67
|
+
cmd = "open";
|
|
68
|
+
args = [url];
|
|
69
|
+
break;
|
|
70
|
+
case "win32":
|
|
71
|
+
cmd = "cmd";
|
|
72
|
+
args = ["/c", "start", "", url];
|
|
73
|
+
break;
|
|
74
|
+
default:
|
|
75
|
+
cmd = "xdg-open";
|
|
76
|
+
args = [url];
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
let child;
|
|
80
|
+
try {
|
|
81
|
+
child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
82
|
+
} catch {
|
|
83
|
+
resolve(false);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
let settled = false;
|
|
87
|
+
const settle = (ok) => {
|
|
88
|
+
if (settled) return;
|
|
89
|
+
settled = true;
|
|
90
|
+
resolve(ok);
|
|
91
|
+
};
|
|
92
|
+
child.on("error", () => settle(false));
|
|
93
|
+
child.on("spawn", () => {
|
|
94
|
+
child.unref();
|
|
95
|
+
// Give a late ENOENT a moment to be delivered as an 'error' event.
|
|
96
|
+
setTimeout(() => settle(true), 150);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Roadmap §2.5 / §9.2 — redaction static guard.
|
|
3
|
+
//
|
|
4
|
+
// Fails (exit 1) if any production source file outside the single
|
|
5
|
+
// permitted module (`auth-health-monitor.ts`) contains a raw SQL
|
|
6
|
+
// assignment to `auth_detail` or `last_error`. The permitted module
|
|
7
|
+
// owns the `writeAuthFailureDetail` / `writeAuthOkDetail` helpers,
|
|
8
|
+
// which apply `redactSensitiveString` unconditionally — every other
|
|
9
|
+
// caller is required to go through them.
|
|
10
|
+
//
|
|
11
|
+
// Exemptions:
|
|
12
|
+
// - test files (*.test.ts) — they seed raw fixtures, not
|
|
13
|
+
// production writes
|
|
14
|
+
// - `auth-health-monitor.ts` — the helpers themselves
|
|
15
|
+
//
|
|
16
|
+
// A match prints file:line:matching-line and exits non-zero. The full
|
|
17
|
+
// scan runs in well under a second; it is intended to be wired into
|
|
18
|
+
// `pnpm test`'s preamble (or a CI step) so that a new offending write
|
|
19
|
+
// cannot silently land alongside a green test suite.
|
|
20
|
+
|
|
21
|
+
import { execFileSync } from "node:child_process";
|
|
22
|
+
import { fileURLToPath } from "node:url";
|
|
23
|
+
import { dirname, relative, resolve } from "node:path";
|
|
24
|
+
import { existsSync } from "node:fs";
|
|
25
|
+
|
|
26
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const root = resolve(here, "..");
|
|
28
|
+
const searchRoot = resolve(root, "packages/daemon/src");
|
|
29
|
+
|
|
30
|
+
if (!existsSync(searchRoot)) {
|
|
31
|
+
console.error(`check-redaction-coverage: ${searchRoot} does not exist`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Pattern matches any assignment target `auth_detail =` or
|
|
36
|
+
// `last_error =`, regardless of placeholder style (`?` / `@name` /
|
|
37
|
+
// `:name` / `NULL` / literal). The rg -P flag uses PCRE so we can
|
|
38
|
+
// write a single union.
|
|
39
|
+
const PATTERN = String.raw`(auth_detail|last_error)\s*=`;
|
|
40
|
+
|
|
41
|
+
const EXEMPT_PATHS = new Set([
|
|
42
|
+
"packages/daemon/src/core/backends/auth-health-monitor.ts",
|
|
43
|
+
// `mail_accounts.last_error` is a separate column from the `backends`
|
|
44
|
+
// table's `last_error` that the redaction helpers guard. The redaction
|
|
45
|
+
// helper enforces scrubbing of model-auth detail strings; mail-poll
|
|
46
|
+
// errors are IMAP/Graph status messages that never carry secrets
|
|
47
|
+
// (credentials live in the encrypted blob store and are never inlined
|
|
48
|
+
// into error text — see mail-poller.ts `handlePollError`).
|
|
49
|
+
"packages/daemon/src/services/mail/account-registry.ts",
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
let rgOutput = "";
|
|
53
|
+
try {
|
|
54
|
+
rgOutput = execFileSync(
|
|
55
|
+
"rg",
|
|
56
|
+
[
|
|
57
|
+
"--no-heading",
|
|
58
|
+
"--line-number",
|
|
59
|
+
"--with-filename",
|
|
60
|
+
"--glob",
|
|
61
|
+
"!**/*.test.ts",
|
|
62
|
+
"--glob",
|
|
63
|
+
"!**/*.d.ts",
|
|
64
|
+
"-e",
|
|
65
|
+
PATTERN,
|
|
66
|
+
searchRoot,
|
|
67
|
+
],
|
|
68
|
+
{ encoding: "utf8" },
|
|
69
|
+
);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
// rg exits 1 when there are zero matches — that is the success case.
|
|
72
|
+
if (err.status === 1 && !err.stdout) {
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
// rg exits 2 on actual errors. Surface them.
|
|
76
|
+
console.error("check-redaction-coverage: rg failed");
|
|
77
|
+
console.error(err.stderr?.toString() ?? err.message);
|
|
78
|
+
process.exit(2);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const lines = rgOutput.split("\n").filter((line) => line.length > 0);
|
|
82
|
+
const offenders = [];
|
|
83
|
+
for (const line of lines) {
|
|
84
|
+
// Each rg line is `absolute-path:line:content`. Convert to a repo
|
|
85
|
+
// relative path so the exemption check is stable.
|
|
86
|
+
const firstColon = line.indexOf(":");
|
|
87
|
+
const secondColon = line.indexOf(":", firstColon + 1);
|
|
88
|
+
if (firstColon < 0 || secondColon < 0) continue;
|
|
89
|
+
const absPath = line.slice(0, firstColon);
|
|
90
|
+
const relPath = relative(root, absPath).replace(/\\/g, "/");
|
|
91
|
+
if (EXEMPT_PATHS.has(relPath)) continue;
|
|
92
|
+
offenders.push(`${relPath}:${line.slice(firstColon + 1)}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (offenders.length > 0) {
|
|
96
|
+
console.error(
|
|
97
|
+
"check-redaction-coverage: found raw auth_detail / last_error writes outside the helper module.",
|
|
98
|
+
);
|
|
99
|
+
console.error(
|
|
100
|
+
"All writes must go through writeAuthFailureDetail / writeAuthOkDetail in packages/daemon/src/core/backends/auth-health-monitor.ts (roadmap §9.2).",
|
|
101
|
+
);
|
|
102
|
+
console.error("");
|
|
103
|
+
for (const line of offenders) {
|
|
104
|
+
console.error(` ${line}`);
|
|
105
|
+
}
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
process.exit(0);
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `aitne audit` — view the agent action log.
|
|
3
|
+
*
|
|
4
|
+
* Reads `agent_actions` directly from SQLite in readonly mode. Safe while
|
|
5
|
+
* the daemon runs because the daemon enables WAL
|
|
6
|
+
* (packages/daemon/src/db/client.ts) — concurrent readers are fine.
|
|
7
|
+
*
|
|
8
|
+
* Default: last 50 rows from the past 24h, table output, summary footer.
|
|
9
|
+
*
|
|
10
|
+
* The `agent_actions` columns we surface are intentionally narrow:
|
|
11
|
+
* started_at, action_type, backend, result, duration_ms, cost_usd
|
|
12
|
+
* Plus `error` and `detail` only on `--detail`. That keeps the default
|
|
13
|
+
* output scannable on an 80-col terminal.
|
|
14
|
+
*/
|
|
15
|
+
import fs from "node:fs";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
|
|
18
|
+
export async function run(args, ctx) {
|
|
19
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
20
|
+
printHelp();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const opts = parseArgs(args);
|
|
25
|
+
const dbPath = path.join(ctx.DATA_DIR, "data", "personal_agent.db");
|
|
26
|
+
if (!fs.existsSync(dbPath)) {
|
|
27
|
+
console.log("No actions logged yet.");
|
|
28
|
+
console.log(`(Database not found at ${dbPath} — run \`aitne start\` first.)`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { loadBetterSqlite3 } = await import("../lib/sqlite-loader.mjs");
|
|
33
|
+
const Database = await loadBetterSqlite3(ctx.PROJECT_ROOT);
|
|
34
|
+
const db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
35
|
+
let rows;
|
|
36
|
+
let summary;
|
|
37
|
+
try {
|
|
38
|
+
rows = queryRows(db, opts);
|
|
39
|
+
summary = querySummary(db, opts);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if (/no such table/i.test(err?.message ?? "")) {
|
|
42
|
+
console.error("agent_actions table not present — schema is older than this CLI.");
|
|
43
|
+
console.error("Try `aitne stop && rm ~/.personal-agent/data/personal_agent.db && aitne start`.");
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
throw err;
|
|
47
|
+
} finally {
|
|
48
|
+
db.close();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (opts.json) {
|
|
52
|
+
process.stdout.write(JSON.stringify({ rows, summary, query: opts }, null, 2) + "\n");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (rows.length === 0) {
|
|
57
|
+
console.log(`No actions in the selected window (since ${opts.since}).`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
printTable(rows, opts);
|
|
62
|
+
printFooter(summary, opts);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
66
|
+
// CLI parsing
|
|
67
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
// Allowed values for enum-style flags. Validated up-front so a typo like
|
|
70
|
+
// `--result fail` becomes an explicit error instead of a silent zero-row
|
|
71
|
+
// query (`WHERE result = 'fail'` matches nothing).
|
|
72
|
+
const VALID_RESULTS = new Set(["success", "failed", "partial", "skipped"]);
|
|
73
|
+
const VALID_BACKENDS = new Set(["claude", "codex", "gemini"]);
|
|
74
|
+
|
|
75
|
+
function parseArgs(args) {
|
|
76
|
+
const opts = {
|
|
77
|
+
since: "24h",
|
|
78
|
+
type: null,
|
|
79
|
+
result: null,
|
|
80
|
+
backend: null,
|
|
81
|
+
limit: 50,
|
|
82
|
+
detail: false,
|
|
83
|
+
json: false,
|
|
84
|
+
};
|
|
85
|
+
// Helper: read the value paired with a flag and fail loudly if missing.
|
|
86
|
+
// Without this, `aitne audit --since` would set opts.since to undefined
|
|
87
|
+
// and produce a misleading "got: undefined" downstream.
|
|
88
|
+
const takeValue = (i, flag) => {
|
|
89
|
+
if (i + 1 >= args.length) {
|
|
90
|
+
console.error(`${flag} requires a value`);
|
|
91
|
+
console.error(`See \`aitne audit --help\`.`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
return args[i + 1];
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < args.length; i++) {
|
|
98
|
+
const a = args[i];
|
|
99
|
+
if (a === "--since") { opts.since = takeValue(i, "--since"); i++; }
|
|
100
|
+
else if (a === "--type") { opts.type = takeValue(i, "--type"); i++; }
|
|
101
|
+
else if (a === "--result") {
|
|
102
|
+
const v = takeValue(i, "--result"); i++;
|
|
103
|
+
if (!VALID_RESULTS.has(v)) {
|
|
104
|
+
console.error(`--result must be one of ${[...VALID_RESULTS].join(" | ")} (got: ${v})`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
opts.result = v;
|
|
108
|
+
}
|
|
109
|
+
else if (a === "--backend") {
|
|
110
|
+
const v = takeValue(i, "--backend"); i++;
|
|
111
|
+
if (!VALID_BACKENDS.has(v)) {
|
|
112
|
+
console.error(`--backend must be one of ${[...VALID_BACKENDS].join(" | ")} (got: ${v})`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
opts.backend = v;
|
|
116
|
+
}
|
|
117
|
+
else if (a === "--limit") {
|
|
118
|
+
const raw = takeValue(i, "--limit"); i++;
|
|
119
|
+
const n = parseInt(raw, 10);
|
|
120
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
121
|
+
console.error(`--limit must be a positive integer (got: ${raw})`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
opts.limit = n;
|
|
125
|
+
}
|
|
126
|
+
else if (a === "--detail") opts.detail = true;
|
|
127
|
+
else if (a === "--json") opts.json = true;
|
|
128
|
+
else {
|
|
129
|
+
console.error(`Unknown flag: ${a}`);
|
|
130
|
+
console.error("See `aitne audit --help`.");
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return opts;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Convert a duration like "24h" / "7d" / "90m" / "2026-04-20" into a SQLite
|
|
139
|
+
* datetime modifier suitable for `datetime('now', '<modifier>')`. Returns
|
|
140
|
+
* the modifier OR an absolute ISO-ish date for direct comparison.
|
|
141
|
+
*/
|
|
142
|
+
function sinceToWhereClause(since) {
|
|
143
|
+
// Absolute date (YYYY-MM-DD).
|
|
144
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(since)) {
|
|
145
|
+
return { sql: "started_at >= ?", param: `${since} 00:00:00` };
|
|
146
|
+
}
|
|
147
|
+
// Relative duration.
|
|
148
|
+
const m = /^(\d+)\s*(s|m|h|d)$/.exec(since);
|
|
149
|
+
if (!m) {
|
|
150
|
+
console.error(`--since must be like "24h", "7d", "90m", or "2026-04-20" (got: ${since}).`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
const n = parseInt(m[1], 10);
|
|
154
|
+
const unit = { s: "seconds", m: "minutes", h: "hours", d: "days" }[m[2]];
|
|
155
|
+
return { sql: `started_at >= datetime('now', ?)`, param: `-${n} ${unit}` };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
159
|
+
// SQL
|
|
160
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
function buildWhere(opts) {
|
|
163
|
+
const clauses = [];
|
|
164
|
+
const params = [];
|
|
165
|
+
const since = sinceToWhereClause(opts.since);
|
|
166
|
+
clauses.push(since.sql);
|
|
167
|
+
params.push(since.param);
|
|
168
|
+
if (opts.type) {
|
|
169
|
+
if (opts.type.includes("%")) { clauses.push("action_type LIKE ?"); params.push(opts.type); }
|
|
170
|
+
else { clauses.push("action_type = ?"); params.push(opts.type); }
|
|
171
|
+
}
|
|
172
|
+
if (opts.result) { clauses.push("result = ?"); params.push(opts.result); }
|
|
173
|
+
if (opts.backend) { clauses.push("backend = ?"); params.push(opts.backend); }
|
|
174
|
+
return { where: clauses.join(" AND "), params };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function queryRows(db, opts) {
|
|
178
|
+
const { where, params } = buildWhere(opts);
|
|
179
|
+
const cols = "started_at, action_type, backend, result, duration_ms, cost_usd"
|
|
180
|
+
+ (opts.detail ? ", error, detail" : "");
|
|
181
|
+
const stmt = db.prepare(
|
|
182
|
+
`SELECT ${cols} FROM agent_actions WHERE ${where} ORDER BY started_at DESC LIMIT ?`,
|
|
183
|
+
);
|
|
184
|
+
return stmt.all(...params, opts.limit);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function querySummary(db, opts) {
|
|
188
|
+
const { where, params } = buildWhere(opts);
|
|
189
|
+
const stmt = db.prepare(
|
|
190
|
+
`SELECT COUNT(*) AS n,
|
|
191
|
+
SUM(CASE WHEN result = 'failed' THEN 1 ELSE 0 END) AS failed,
|
|
192
|
+
SUM(CASE WHEN result = 'success' THEN 1 ELSE 0 END) AS success,
|
|
193
|
+
COALESCE(SUM(cost_usd), 0) AS cost,
|
|
194
|
+
(SELECT action_type FROM agent_actions WHERE ${where}
|
|
195
|
+
GROUP BY action_type ORDER BY COUNT(*) DESC LIMIT 1) AS top_type
|
|
196
|
+
FROM agent_actions WHERE ${where}`,
|
|
197
|
+
);
|
|
198
|
+
// Inline the params twice — once for the outer query, once for the
|
|
199
|
+
// correlated subquery — since better-sqlite3 doesn't reuse positional
|
|
200
|
+
// params across nested SELECTs.
|
|
201
|
+
return stmt.get(...params, ...params);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
205
|
+
// Output
|
|
206
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
function printTable(rows, opts) {
|
|
209
|
+
// Column widths chosen for typical content; clamp action_type so a long
|
|
210
|
+
// event name doesn't push everything off-screen.
|
|
211
|
+
const headers = ["TIME", "TYPE", "BACKEND", "RESULT", "DURATION", "COST"];
|
|
212
|
+
const widths = [19, 32, 8, 8, 10, 8];
|
|
213
|
+
console.log(headers.map((h, i) => h.padEnd(widths[i])).join(" "));
|
|
214
|
+
for (const r of rows) {
|
|
215
|
+
const t = (r.started_at ?? "").slice(0, 19);
|
|
216
|
+
const type = clamp(r.action_type ?? "", widths[1]);
|
|
217
|
+
const be = (r.backend ?? "").slice(0, widths[2]);
|
|
218
|
+
const res = (r.result ?? "—").slice(0, widths[3]);
|
|
219
|
+
const dur = formatDuration(r.duration_ms);
|
|
220
|
+
const cost = formatCost(r.cost_usd);
|
|
221
|
+
console.log([
|
|
222
|
+
t.padEnd(widths[0]),
|
|
223
|
+
type.padEnd(widths[1]),
|
|
224
|
+
be.padEnd(widths[2]),
|
|
225
|
+
res.padEnd(widths[3]),
|
|
226
|
+
dur.padStart(widths[4]),
|
|
227
|
+
cost.padStart(widths[5]),
|
|
228
|
+
].join(" "));
|
|
229
|
+
if (opts.detail) {
|
|
230
|
+
if (r.error) console.log(` error: ${r.error}`);
|
|
231
|
+
if (r.detail) {
|
|
232
|
+
// Pretty-print one line per top-level key to keep --detail readable.
|
|
233
|
+
try {
|
|
234
|
+
const parsed = typeof r.detail === "string" ? JSON.parse(r.detail) : r.detail;
|
|
235
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
236
|
+
const printable = typeof v === "string" ? v : JSON.stringify(v);
|
|
237
|
+
console.log(` ${k}: ${truncate(printable, 120)}`);
|
|
238
|
+
}
|
|
239
|
+
} catch { console.log(` detail: ${truncate(String(r.detail), 120)}`); }
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function printFooter(s, opts) {
|
|
246
|
+
if (!s || !s.n) return;
|
|
247
|
+
const failed = Number(s.failed ?? 0);
|
|
248
|
+
const cost = Number(s.cost ?? 0);
|
|
249
|
+
const top = s.top_type ?? "—";
|
|
250
|
+
console.log("");
|
|
251
|
+
console.log(`${s.n} action(s) since ${opts.since} · ${failed} failed · $${cost.toFixed(3)} spent · top: ${top}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ── formatting helpers ──
|
|
255
|
+
|
|
256
|
+
function formatDuration(ms) {
|
|
257
|
+
const n = Number(ms);
|
|
258
|
+
if (!Number.isFinite(n) || n <= 0) return "—";
|
|
259
|
+
if (n < 1000) return `${n}ms`;
|
|
260
|
+
const s = n / 1000;
|
|
261
|
+
if (s < 60) return `${s.toFixed(1)}s`;
|
|
262
|
+
const m = s / 60;
|
|
263
|
+
if (m < 60) return `${m.toFixed(1)}m`;
|
|
264
|
+
return `${(m / 60).toFixed(1)}h`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function formatCost(c) {
|
|
268
|
+
const n = Number(c);
|
|
269
|
+
if (!Number.isFinite(n) || n === 0) return "$0.000";
|
|
270
|
+
return `$${n.toFixed(3)}`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function clamp(s, w) {
|
|
274
|
+
if (s.length <= w) return s;
|
|
275
|
+
return s.slice(0, w - 1) + "…";
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function truncate(s, n) {
|
|
279
|
+
if (s.length <= n) return s;
|
|
280
|
+
return s.slice(0, n - 1) + "…";
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function printHelp() {
|
|
284
|
+
console.log(`Usage: aitne audit [flags]
|
|
285
|
+
|
|
286
|
+
Show the agent action log (\`agent_actions\` table) — what the agent has been
|
|
287
|
+
doing, when, on which backend, with what result, and how much it cost.
|
|
288
|
+
|
|
289
|
+
Default: last 50 rows from the past 24h.
|
|
290
|
+
|
|
291
|
+
Flags:
|
|
292
|
+
--since <duration> Window. Examples: 1h, 90m, 7d, 2026-04-20. Default: 24h.
|
|
293
|
+
--type <pattern> Filter on action_type. Use % for LIKE wildcard.
|
|
294
|
+
Examples: --type routine.morning_routine
|
|
295
|
+
--type "routine.%"
|
|
296
|
+
--result <value> Filter on result column: success | failed | partial | skipped.
|
|
297
|
+
--backend <id> Filter on backend: claude | codex | gemini.
|
|
298
|
+
--limit <N> Max rows. Default: 50.
|
|
299
|
+
--detail Append the \`error\` and \`detail\` columns under each row.
|
|
300
|
+
--json Machine-readable output (suppresses the summary footer).
|
|
301
|
+
|
|
302
|
+
Examples:
|
|
303
|
+
aitne audit # last 24h, default columns
|
|
304
|
+
aitne audit --since 7d # last week
|
|
305
|
+
aitne audit --result failed # only failures
|
|
306
|
+
aitne audit --type "routine.%" --limit 100 # all routines in last 24h, 100 rows
|
|
307
|
+
aitne audit --type blocked_absolute # everything the absolute-block layer caught
|
|
308
|
+
aitne audit --json | jq '.summary' # programmatic`);
|
|
309
|
+
}
|