@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
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Smart build-gate runner.
|
|
10
|
+
*
|
|
11
|
+
* 1. Checks whether TypeScript sources are newer than .buildstamp
|
|
12
|
+
* 2. If stale, runs `turbo run build` (respects shared → daemon dep order)
|
|
13
|
+
* 3. Spawns `node personal-agent.mjs` with the compiled output
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const SOURCE_DIRS = ["packages/shared/src", "packages/daemon/src", "packages/dashboard/src"];
|
|
17
|
+
const CONFIG_FILES = [
|
|
18
|
+
"tsconfig.base.json",
|
|
19
|
+
"packages/shared/tsconfig.json",
|
|
20
|
+
"packages/shared/package.json",
|
|
21
|
+
"packages/daemon/tsconfig.json",
|
|
22
|
+
"packages/daemon/package.json",
|
|
23
|
+
"packages/dashboard/package.json",
|
|
24
|
+
"packages/dashboard/next.config.ts",
|
|
25
|
+
"packages/dashboard/postcss.config.mjs",
|
|
26
|
+
"package.json",
|
|
27
|
+
];
|
|
28
|
+
const DIST_ENTRY = "packages/daemon/dist/index.js";
|
|
29
|
+
const DASHBOARD_BUILD_ID = "packages/dashboard/.next/BUILD_ID";
|
|
30
|
+
|
|
31
|
+
const statMtime = (filePath) => {
|
|
32
|
+
try {
|
|
33
|
+
return fs.statSync(filePath).mtimeMs;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const isExcludedSource = (filePath) => {
|
|
40
|
+
return (
|
|
41
|
+
filePath.endsWith(".test.ts") ||
|
|
42
|
+
filePath.endsWith(".test.tsx") ||
|
|
43
|
+
filePath.endsWith("test-helpers.ts")
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const findLatestMtime = (dirPath) => {
|
|
48
|
+
let latest = null;
|
|
49
|
+
const queue = [dirPath];
|
|
50
|
+
while (queue.length > 0) {
|
|
51
|
+
const current = queue.pop();
|
|
52
|
+
if (!current) continue;
|
|
53
|
+
let entries;
|
|
54
|
+
try {
|
|
55
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
56
|
+
} catch {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
const fullPath = path.join(current, entry.name);
|
|
61
|
+
if (entry.isDirectory()) {
|
|
62
|
+
queue.push(fullPath);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (!entry.isFile()) continue;
|
|
66
|
+
if (isExcludedSource(fullPath)) continue;
|
|
67
|
+
const mtime = statMtime(fullPath);
|
|
68
|
+
if (mtime != null && (latest == null || mtime > latest)) {
|
|
69
|
+
latest = mtime;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return latest;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const readBuildStamp = (stampPath) => {
|
|
77
|
+
const mtime = statMtime(stampPath);
|
|
78
|
+
if (mtime == null) return { mtime: null };
|
|
79
|
+
return { mtime };
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
function hasAnySourceTree(cwd) {
|
|
83
|
+
return SOURCE_DIRS.some((relDir) => {
|
|
84
|
+
try {
|
|
85
|
+
return fs.statSync(path.join(cwd, relDir)).isDirectory();
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* True when running from a published `pnpm add -g aitne` install. The aitne
|
|
94
|
+
* tarball excludes packages/ — sub-packages live as siblings under node_modules
|
|
95
|
+
* (@aitne/daemon, @aitne/dashboard, @aitne/shared) and ship their own dist/.
|
|
96
|
+
* In this layout there is nothing to build at the consumer's cwd, and the
|
|
97
|
+
* build toolchain (turbo, tsc, next) is not installed.
|
|
98
|
+
*/
|
|
99
|
+
export function isPublishedInstall(cwd) {
|
|
100
|
+
return !hasAnySourceTree(cwd);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function hasPackagedBuild(cwd) {
|
|
104
|
+
return (
|
|
105
|
+
statMtime(path.join(cwd, DIST_ENTRY)) != null &&
|
|
106
|
+
statMtime(path.join(cwd, DASHBOARD_BUILD_ID)) != null &&
|
|
107
|
+
!hasAnySourceTree(cwd)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Check if daemon TypeScript needs rebuilding. */
|
|
112
|
+
export function shouldBuild(cwd) {
|
|
113
|
+
// Published install: nothing to build at cwd, build toolchain not installed.
|
|
114
|
+
if (isPublishedInstall(cwd)) return false;
|
|
115
|
+
// Legacy fallback: source-less tree with prebuilt artifacts at cwd.
|
|
116
|
+
if (hasPackagedBuild(cwd)) return false;
|
|
117
|
+
|
|
118
|
+
const stampPath = path.join(cwd, ".buildstamp");
|
|
119
|
+
|
|
120
|
+
if (process.env.PA_FORCE_BUILD === "1") return true;
|
|
121
|
+
|
|
122
|
+
const stamp = readBuildStamp(stampPath);
|
|
123
|
+
if (stamp.mtime == null) return true;
|
|
124
|
+
|
|
125
|
+
if (statMtime(path.join(cwd, DIST_ENTRY)) == null) return true;
|
|
126
|
+
if (statMtime(path.join(cwd, DASHBOARD_BUILD_ID)) == null) return true;
|
|
127
|
+
|
|
128
|
+
for (const relPath of CONFIG_FILES) {
|
|
129
|
+
const mtime = statMtime(path.join(cwd, relPath));
|
|
130
|
+
if (mtime != null && mtime > stamp.mtime) return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const relDir of SOURCE_DIRS) {
|
|
134
|
+
const srcMtime = findLatestMtime(path.join(cwd, relDir));
|
|
135
|
+
if (srcMtime != null && srcMtime > stamp.mtime) return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Write .buildstamp after a successful build. */
|
|
142
|
+
export function writeBuildStamp(cwd) {
|
|
143
|
+
const stampPath = path.join(cwd, ".buildstamp");
|
|
144
|
+
try {
|
|
145
|
+
const stamp = { builtAt: Date.now() };
|
|
146
|
+
fs.writeFileSync(stampPath, `${JSON.stringify(stamp)}\n`);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
log(`Failed to write build stamp: ${error?.message ?? "unknown error"}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Run turbo build. Returns exit code. */
|
|
153
|
+
export async function runBuild(cwd, { quiet = false } = {}) {
|
|
154
|
+
if (!quiet) log("Building TypeScript (dist is stale).");
|
|
155
|
+
const stdio = quiet ? ["ignore", "pipe", "pipe"] : "inherit";
|
|
156
|
+
// pnpm is installed as `pnpm.cmd` (and `.ps1`) on Windows. Batch shims
|
|
157
|
+
// require a shell on Windows; POSIX keeps shell:false for predictable argv
|
|
158
|
+
// handling.
|
|
159
|
+
const pnpmBin = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
|
|
160
|
+
const build = spawn(pnpmBin, ["run", "build"], {
|
|
161
|
+
cwd,
|
|
162
|
+
env: process.env,
|
|
163
|
+
stdio,
|
|
164
|
+
windowsHide: true,
|
|
165
|
+
shell: process.platform === "win32",
|
|
166
|
+
});
|
|
167
|
+
let output = "";
|
|
168
|
+
if (quiet) {
|
|
169
|
+
build.stdout?.on("data", (chunk) => { output += chunk; });
|
|
170
|
+
build.stderr?.on("data", (chunk) => { output += chunk; });
|
|
171
|
+
}
|
|
172
|
+
const res = await new Promise((resolve) => {
|
|
173
|
+
build.on("exit", (exitCode, exitSignal) => resolve({ exitCode, exitSignal }));
|
|
174
|
+
});
|
|
175
|
+
const code = res.exitSignal ? 1 : (res.exitCode ?? 0);
|
|
176
|
+
if (quiet && code !== 0) {
|
|
177
|
+
process.stderr.write(output);
|
|
178
|
+
}
|
|
179
|
+
return code;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Ensure build is fresh. Returns exit code (0 = ok). */
|
|
183
|
+
export async function ensureBuild(cwd, { quiet = false } = {}) {
|
|
184
|
+
if (!shouldBuild(cwd)) return 0;
|
|
185
|
+
const code = await runBuild(cwd, { quiet });
|
|
186
|
+
if (code === 0) writeBuildStamp(cwd);
|
|
187
|
+
return code;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function log(message) {
|
|
191
|
+
if (process.env.PA_RUNNER_LOG === "0") return;
|
|
192
|
+
process.stderr.write(`[aiservant] ${message}\n`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ── Direct execution: build-if-needed → run daemon ──
|
|
196
|
+
|
|
197
|
+
async function runNodeMain() {
|
|
198
|
+
const cwd = process.cwd();
|
|
199
|
+
const args = process.argv.slice(2);
|
|
200
|
+
|
|
201
|
+
const buildCode = await ensureBuild(cwd);
|
|
202
|
+
if (buildCode !== 0) return buildCode;
|
|
203
|
+
|
|
204
|
+
const child = spawn(process.execPath, ["personal-agent.mjs", ...args], {
|
|
205
|
+
cwd,
|
|
206
|
+
env: process.env,
|
|
207
|
+
stdio: "inherit",
|
|
208
|
+
});
|
|
209
|
+
const res = await new Promise((resolve) => {
|
|
210
|
+
child.on("exit", (exitCode, exitSignal) => resolve({ exitCode, exitSignal }));
|
|
211
|
+
});
|
|
212
|
+
if (res.exitSignal) return 1;
|
|
213
|
+
return res.exitCode ?? 1;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
|
217
|
+
void runNodeMain()
|
|
218
|
+
.then((code) => process.exit(code))
|
|
219
|
+
.catch((err) => {
|
|
220
|
+
console.error(err);
|
|
221
|
+
process.exit(1);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* In-process smoke test for the Obsidian API routes.
|
|
4
|
+
*
|
|
5
|
+
* Purpose: verify empirically that the new PUT/DELETE endpoints — and
|
|
6
|
+
* the `path=` CLI semantics they rely on — actually work against the
|
|
7
|
+
* real Obsidian CLI and a real vault. The vitest suite mocks the
|
|
8
|
+
* ObsidianService entirely, so it cannot catch wrong argument names,
|
|
9
|
+
* missing CLI flags, or vault-level semantic mismatches.
|
|
10
|
+
*
|
|
11
|
+
* Strategy: mount the freshly-built routes against a real
|
|
12
|
+
* ObsidianService pointing at the real vault, then drive the full
|
|
13
|
+
* CRUD lifecycle via Hono's in-process `app.request`. No daemon
|
|
14
|
+
* restart, no network, no port conflict.
|
|
15
|
+
*
|
|
16
|
+
* Safety:
|
|
17
|
+
* - All writes target a single `__smoketest_obsidian_api.md` path
|
|
18
|
+
* prefixed with `__` so it's visually obvious in the vault.
|
|
19
|
+
* - The lifecycle ends with DELETE, so a successful run leaves no
|
|
20
|
+
* residue.
|
|
21
|
+
* - On any failure we attempt a best-effort DELETE before exiting.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { createObsidianRoutes } from "../packages/daemon/dist/api/routes/obsidian.js";
|
|
25
|
+
import { ObsidianService } from "../packages/daemon/dist/services/obsidian.js";
|
|
26
|
+
|
|
27
|
+
const TEST_PATH = "__smoketest_obsidian_api";
|
|
28
|
+
const VAULT_PATH =
|
|
29
|
+
process.env.PA_EXTERNAL_OBSIDIAN_VAULT_PATH ??
|
|
30
|
+
process.env.PA_OBSIDIAN_VAULT_PATH ??
|
|
31
|
+
"/Users/test/Library/Mobile Documents/iCloud~md~obsidian/Documents/personal-agent";
|
|
32
|
+
const VAULT_NAME =
|
|
33
|
+
process.env.PA_EXTERNAL_OBSIDIAN_VAULT_NAME ??
|
|
34
|
+
process.env.PA_OBSIDIAN_VAULT_NAME ??
|
|
35
|
+
"personal-agent";
|
|
36
|
+
|
|
37
|
+
const config = {
|
|
38
|
+
externalObsidianVaultPath: VAULT_PATH,
|
|
39
|
+
externalObsidianVaultName: VAULT_NAME,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const service = new ObsidianService(config);
|
|
43
|
+
const app = createObsidianRoutes({ obsidianService: service });
|
|
44
|
+
|
|
45
|
+
let pass = 0;
|
|
46
|
+
let fail = 0;
|
|
47
|
+
const failures = [];
|
|
48
|
+
|
|
49
|
+
function ok(label, cond, detail) {
|
|
50
|
+
if (cond) {
|
|
51
|
+
pass += 1;
|
|
52
|
+
console.log(` \u2713 ${label}`);
|
|
53
|
+
} else {
|
|
54
|
+
fail += 1;
|
|
55
|
+
failures.push(`${label}${detail ? ` — ${detail}` : ""}`);
|
|
56
|
+
console.log(` \u2717 ${label}${detail ? ` — ${detail}` : ""}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function request(method, path, body) {
|
|
61
|
+
const init = { method };
|
|
62
|
+
if (body !== undefined) {
|
|
63
|
+
init.headers = { "Content-Type": "application/json" };
|
|
64
|
+
init.body = JSON.stringify(body);
|
|
65
|
+
}
|
|
66
|
+
const res = await app.request(path, init);
|
|
67
|
+
let json = null;
|
|
68
|
+
try {
|
|
69
|
+
json = await res.json();
|
|
70
|
+
} catch {}
|
|
71
|
+
return { status: res.status, json };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function bestEffortCleanup() {
|
|
75
|
+
try {
|
|
76
|
+
await request("DELETE", `/obsidian/notes/${TEST_PATH}`);
|
|
77
|
+
} catch {}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function main() {
|
|
81
|
+
console.log(`Vault: ${VAULT_PATH}`);
|
|
82
|
+
console.log(`Test path: ${TEST_PATH}.md`);
|
|
83
|
+
console.log();
|
|
84
|
+
|
|
85
|
+
// --- Precondition: status ---
|
|
86
|
+
console.log("1. Precondition");
|
|
87
|
+
const status = await request("GET", "/obsidian/status");
|
|
88
|
+
ok("status=200", status.status === 200);
|
|
89
|
+
ok("available=true", status.json?.available === true, JSON.stringify(status.json));
|
|
90
|
+
ok("obsidianRunning=true", status.json?.obsidianRunning === true, JSON.stringify(status.json));
|
|
91
|
+
if (status.json?.obsidianRunning !== true) {
|
|
92
|
+
console.error("\nObsidian app is not running — aborting smoke test.");
|
|
93
|
+
process.exit(2);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Make sure we start clean (in case a previous run crashed mid-lifecycle).
|
|
97
|
+
await bestEffortCleanup();
|
|
98
|
+
|
|
99
|
+
// --- Step 2: PUT creates a new note (idempotent create-or-replace) ---
|
|
100
|
+
console.log("\n2. PUT create-path");
|
|
101
|
+
const body1 = "# Smoke test v1\n\nLine A\n";
|
|
102
|
+
const put1 = await request("PUT", `/obsidian/notes/${TEST_PATH}`, { content: body1 });
|
|
103
|
+
ok("PUT status=200", put1.status === 200, `got ${put1.status} ${JSON.stringify(put1.json)}`);
|
|
104
|
+
ok("PUT status field=updated", put1.json?.status === "updated");
|
|
105
|
+
|
|
106
|
+
// --- Step 3: GET returns the created content ---
|
|
107
|
+
console.log("\n3. GET read back");
|
|
108
|
+
const get1 = await request("GET", `/obsidian/notes/${TEST_PATH}`);
|
|
109
|
+
ok("GET status=200", get1.status === 200);
|
|
110
|
+
ok(
|
|
111
|
+
"GET content matches v1",
|
|
112
|
+
typeof get1.json?.content === "string" && get1.json.content.includes("Line A"),
|
|
113
|
+
`got ${JSON.stringify(get1.json?.content)?.slice(0, 80)}`,
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// --- Step 4: PUT overwrites with new content ---
|
|
117
|
+
console.log("\n4. PUT overwrite");
|
|
118
|
+
const body2 = "# Smoke test v2\n\nLine B only\n";
|
|
119
|
+
const put2 = await request("PUT", `/obsidian/notes/${TEST_PATH}`, { content: body2 });
|
|
120
|
+
ok("PUT status=200", put2.status === 200);
|
|
121
|
+
|
|
122
|
+
const get2 = await request("GET", `/obsidian/notes/${TEST_PATH}`);
|
|
123
|
+
ok("GET status=200", get2.status === 200);
|
|
124
|
+
ok(
|
|
125
|
+
"GET content matches v2",
|
|
126
|
+
typeof get2.json?.content === "string"
|
|
127
|
+
&& get2.json.content.includes("Line B only")
|
|
128
|
+
&& !get2.json.content.includes("Line A"),
|
|
129
|
+
`got ${JSON.stringify(get2.json?.content)?.slice(0, 80)}`,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// --- Step 5: DELETE removes the note ---
|
|
133
|
+
console.log("\n5. DELETE");
|
|
134
|
+
const del1 = await request("DELETE", `/obsidian/notes/${TEST_PATH}`);
|
|
135
|
+
ok("DELETE status=200", del1.status === 200, `got ${del1.status} ${JSON.stringify(del1.json)}`);
|
|
136
|
+
ok("DELETE status field=deleted", del1.json?.status === "deleted");
|
|
137
|
+
ok("DELETE permanent=false (default)", del1.json?.permanent === false);
|
|
138
|
+
|
|
139
|
+
// --- Step 6: DELETE again → 404 (idempotent) ---
|
|
140
|
+
console.log("\n6. DELETE idempotent 404");
|
|
141
|
+
const del2 = await request("DELETE", `/obsidian/notes/${TEST_PATH}`);
|
|
142
|
+
ok("Second DELETE status=404", del2.status === 404, `got ${del2.status} ${JSON.stringify(del2.json)}`);
|
|
143
|
+
ok("error=not_found", del2.json?.error === "not_found");
|
|
144
|
+
|
|
145
|
+
// --- Step 7: GET after delete → 404 ---
|
|
146
|
+
console.log("\n7. GET after delete");
|
|
147
|
+
const get3 = await request("GET", `/obsidian/notes/${TEST_PATH}`);
|
|
148
|
+
ok("GET status=404", get3.status === 404, `got ${get3.status} ${JSON.stringify(get3.json)}`);
|
|
149
|
+
|
|
150
|
+
// --- Summary ---
|
|
151
|
+
console.log();
|
|
152
|
+
console.log("─".repeat(48));
|
|
153
|
+
console.log(`Passed: ${pass} Failed: ${fail}`);
|
|
154
|
+
if (fail > 0) {
|
|
155
|
+
console.log("\nFailures:");
|
|
156
|
+
for (const f of failures) console.log(` - ${f}`);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
console.log("All smoke checks passed.");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
main().catch(async (err) => {
|
|
163
|
+
console.error("Smoke test threw:", err);
|
|
164
|
+
await bestEffortCleanup();
|
|
165
|
+
process.exit(1);
|
|
166
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import { ensureBuild, log } from "./run-node.mjs";
|
|
8
|
+
import { openBrowser, waitForHttpReady } from "./browser.mjs";
|
|
9
|
+
|
|
10
|
+
const IS_WINDOWS = process.platform === "win32";
|
|
11
|
+
const requireFromScript = createRequire(import.meta.url);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the `next` binary for the current OS — see bin/aitne.mjs
|
|
15
|
+
* for the full rationale. Duplicated rather than shared to keep these
|
|
16
|
+
* scripts dependency-free of one another at module-load time.
|
|
17
|
+
*/
|
|
18
|
+
function resolveNextBin(dashboardDir) {
|
|
19
|
+
try {
|
|
20
|
+
requireFromScript.resolve("next/dist/bin/next");
|
|
21
|
+
return process.execPath;
|
|
22
|
+
} catch {
|
|
23
|
+
// Fall through to legacy .bin probing.
|
|
24
|
+
}
|
|
25
|
+
const binDir = path.join(dashboardDir, "node_modules", ".bin");
|
|
26
|
+
if (IS_WINDOWS) {
|
|
27
|
+
const direct = path.join(dashboardDir, "node_modules", "next", "dist", "bin", "next");
|
|
28
|
+
if (fs.existsSync(direct)) return process.execPath;
|
|
29
|
+
const cmd = path.join(binDir, "next.cmd");
|
|
30
|
+
if (fs.existsSync(cmd)) return cmd;
|
|
31
|
+
}
|
|
32
|
+
return path.join(binDir, "next");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function nextSpawnArgs(dashboardDir, nextBin, userArgs) {
|
|
36
|
+
if (nextBin === process.execPath) {
|
|
37
|
+
try {
|
|
38
|
+
return [requireFromScript.resolve("next/dist/bin/next"), ...userArgs];
|
|
39
|
+
} catch {
|
|
40
|
+
// Fall through to Windows legacy direct-path probing.
|
|
41
|
+
}
|
|
42
|
+
const direct = path.join(
|
|
43
|
+
dashboardDir, "node_modules", "next", "dist", "bin", "next",
|
|
44
|
+
);
|
|
45
|
+
return [direct, ...userArgs];
|
|
46
|
+
}
|
|
47
|
+
return userArgs;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Start daemon + dashboard together.
|
|
52
|
+
*
|
|
53
|
+
* 1. Build TypeScript if stale
|
|
54
|
+
* 2. Spawn daemon (personal-agent.mjs)
|
|
55
|
+
* 3. Spawn dashboard (next dev)
|
|
56
|
+
* 4. Auto-open browser when dashboard is ready (unless --no-open)
|
|
57
|
+
* 5. Ctrl+C stops both
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
const DASHBOARD_PORT = parseInt(process.env.PA_DASHBOARD_PORT || "3000", 10);
|
|
61
|
+
const noOpen = process.argv.slice(2).includes("--no-open");
|
|
62
|
+
const children = [];
|
|
63
|
+
let shuttingDown = false;
|
|
64
|
+
|
|
65
|
+
// ── 1. Build ──
|
|
66
|
+
|
|
67
|
+
const cwd = process.cwd();
|
|
68
|
+
const buildCode = await ensureBuild(cwd);
|
|
69
|
+
if (buildCode !== 0) process.exit(buildCode);
|
|
70
|
+
|
|
71
|
+
// ── 2. Spawn daemon ──
|
|
72
|
+
|
|
73
|
+
log("Starting daemon...");
|
|
74
|
+
const daemon = spawn(process.execPath, ["personal-agent.mjs"], {
|
|
75
|
+
cwd,
|
|
76
|
+
env: process.env,
|
|
77
|
+
stdio: "inherit",
|
|
78
|
+
});
|
|
79
|
+
children.push(daemon);
|
|
80
|
+
|
|
81
|
+
// ── 3. Spawn dashboard ──
|
|
82
|
+
|
|
83
|
+
log("Starting dashboard...");
|
|
84
|
+
// Resolve dashboard via package.json so this works in both workspace dev
|
|
85
|
+
// (pnpm symlinks node_modules/@aitne/dashboard → packages/dashboard) and
|
|
86
|
+
// global installs (where @aitne/dashboard is a sibling node_modules entry).
|
|
87
|
+
let dashboardDir;
|
|
88
|
+
try {
|
|
89
|
+
dashboardDir = path.dirname(requireFromScript.resolve("@aitne/dashboard/package.json"));
|
|
90
|
+
} catch {
|
|
91
|
+
dashboardDir = path.join(cwd, "packages/dashboard");
|
|
92
|
+
}
|
|
93
|
+
const nextBin = resolveNextBin(dashboardDir);
|
|
94
|
+
const dashArgs = nextSpawnArgs(dashboardDir, nextBin, [
|
|
95
|
+
"dev", "--port", String(DASHBOARD_PORT),
|
|
96
|
+
]);
|
|
97
|
+
const dashboard = spawn(nextBin, dashArgs, {
|
|
98
|
+
cwd: dashboardDir,
|
|
99
|
+
env: process.env,
|
|
100
|
+
stdio: "inherit",
|
|
101
|
+
windowsHide: true,
|
|
102
|
+
shell: IS_WINDOWS && nextBin.toLowerCase().endsWith(".cmd"),
|
|
103
|
+
});
|
|
104
|
+
children.push(dashboard);
|
|
105
|
+
|
|
106
|
+
// ── 4. Auto-open browser ──
|
|
107
|
+
|
|
108
|
+
if (!noOpen) {
|
|
109
|
+
const url = `http://localhost:${DASHBOARD_PORT}`;
|
|
110
|
+
waitForHttpReady(url, {
|
|
111
|
+
// `next dev` can take ~30s on a cold boot; give it headroom.
|
|
112
|
+
timeoutMs: 60_000,
|
|
113
|
+
liveness: [
|
|
114
|
+
() => !shuttingDown,
|
|
115
|
+
() => !daemon.killed,
|
|
116
|
+
() => !dashboard.killed,
|
|
117
|
+
],
|
|
118
|
+
})
|
|
119
|
+
.then(async (ready) => {
|
|
120
|
+
if (!ready) {
|
|
121
|
+
if (!shuttingDown) log("Dashboard did not become ready in time");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
log(`Dashboard ready — opening ${url}`);
|
|
125
|
+
await openBrowser(url);
|
|
126
|
+
})
|
|
127
|
+
.catch((err) => {
|
|
128
|
+
if (!shuttingDown) log(`Browser-open error: ${err?.message ?? err}`);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── 5. Graceful shutdown ──
|
|
133
|
+
|
|
134
|
+
function shutdown(signal) {
|
|
135
|
+
if (shuttingDown) return;
|
|
136
|
+
shuttingDown = true;
|
|
137
|
+
log(`${signal} received, shutting down...`);
|
|
138
|
+
for (const child of children) {
|
|
139
|
+
if (!child.killed) child.kill(signal);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
144
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
145
|
+
if (process.platform === "win32") {
|
|
146
|
+
process.on("SIGBREAK", () => shutdown("SIGBREAK"));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Exit when both children exit
|
|
150
|
+
let exited = 0;
|
|
151
|
+
const exitCodes = [];
|
|
152
|
+
for (const child of children) {
|
|
153
|
+
child.on("exit", (code) => {
|
|
154
|
+
exitCodes.push(code ?? 1);
|
|
155
|
+
exited++;
|
|
156
|
+
if (exited >= children.length) {
|
|
157
|
+
process.exit(Math.max(...exitCodes));
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|