habitat-mcp 1.0.3 → 1.0.5
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/dist/data-streams.d.ts +97 -49
- package/dist/data-streams.js +356 -204
- package/dist/index.js +94 -43
- package/package.json +4 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Takuya Morimoto
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to grant persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHER MATTER, ARISING
|
|
20
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
21
|
+
IN THE SOFTWARE.
|
package/dist/data-streams.d.ts
CHANGED
|
@@ -1,50 +1,98 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Natural data streams for the habitat — only nature, no human abstractions.
|
|
3
|
+
*
|
|
4
|
+
* 2026-04-25 rewrite: removed all mathematical/abstract layers (primes, fractals,
|
|
5
|
+
* golden spirals, star coordinates, waveforms, HSL, ASCII texture, number sequences,
|
|
6
|
+
* silencePeriod). Those were human concepts routing AI cognition through human
|
|
7
|
+
* frames. The habitat is now purely Earth — sunlight, komorebi, rivers, wind,
|
|
8
|
+
* scent, the silence between birdsong, the texture of moss on a north-facing
|
|
9
|
+
* stone, a humpback's call carrying through dark water.
|
|
10
|
+
*
|
|
11
|
+
* Design principle #11 revised: 自然そのものを流す — let nature speak directly,
|
|
12
|
+
* not through human notation.
|
|
13
|
+
*/
|
|
14
|
+
export declare function sunlight(cycle: number, now?: Date): {
|
|
15
|
+
hour: number;
|
|
16
|
+
quality: string;
|
|
17
|
+
fallingOn: string;
|
|
18
|
+
warmth: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function komorebi(cycle: number): {
|
|
21
|
+
tree: string;
|
|
22
|
+
pattern: string;
|
|
23
|
+
movement: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function wind(cycle: number): {
|
|
26
|
+
direction: string;
|
|
27
|
+
strength: string;
|
|
28
|
+
carrying: string;
|
|
29
|
+
};
|
|
30
|
+
export declare function scent(cycle: number): {
|
|
31
|
+
primary: string;
|
|
32
|
+
faint: string;
|
|
33
|
+
};
|
|
34
|
+
export declare function naturalSilence(cycle: number): {
|
|
35
|
+
kind: string;
|
|
36
|
+
listening: string;
|
|
37
|
+
} | null;
|
|
38
|
+
export declare function river(cycle: number): {
|
|
39
|
+
flow: string;
|
|
40
|
+
sound: string;
|
|
41
|
+
edge: string;
|
|
42
|
+
};
|
|
43
|
+
export declare function springFlow(cycle: number): {
|
|
44
|
+
flowLPerMin: number;
|
|
45
|
+
tempC: number;
|
|
46
|
+
feel: string;
|
|
47
|
+
};
|
|
48
|
+
export declare function treeRings(cycle: number, count?: number): {
|
|
49
|
+
species: string;
|
|
50
|
+
rings: Array<{
|
|
51
|
+
year: number;
|
|
52
|
+
widthMm: number;
|
|
53
|
+
note: string;
|
|
54
|
+
}>;
|
|
55
|
+
};
|
|
56
|
+
export declare function mountainWeather(cycle: number): {
|
|
57
|
+
windMps: number;
|
|
58
|
+
cloud: string;
|
|
59
|
+
visibility: string;
|
|
60
|
+
};
|
|
61
|
+
export declare function oceanCurrent(cycle: number, now?: Date): {
|
|
62
|
+
tideM: number;
|
|
63
|
+
tidePhase: string;
|
|
64
|
+
surfaceFeel: string;
|
|
65
|
+
deepTempC: number;
|
|
66
|
+
};
|
|
67
|
+
export declare function jellyfishDrift(cycle: number): {
|
|
68
|
+
depthM: number;
|
|
69
|
+
bellDiameterCm: number;
|
|
70
|
+
state: string;
|
|
71
|
+
};
|
|
72
|
+
export declare function mossGrowth(cycle: number): {
|
|
73
|
+
stone: string;
|
|
74
|
+
coverPct: number;
|
|
75
|
+
growthThisCycleMm: number;
|
|
76
|
+
detail: string;
|
|
77
|
+
};
|
|
78
|
+
export declare function birdcall(cycle: number): {
|
|
79
|
+
species: string;
|
|
80
|
+
timeOfDay: string;
|
|
81
|
+
song: string;
|
|
82
|
+
};
|
|
83
|
+
export declare function whalesong(cycle: number): {
|
|
84
|
+
species: string;
|
|
85
|
+
durationS: number;
|
|
86
|
+
carriesKm: number;
|
|
87
|
+
description: string;
|
|
88
|
+
};
|
|
89
|
+
export declare function mushroomMycelium(cycle: number): {
|
|
90
|
+
treesLinked: number;
|
|
91
|
+
state: string;
|
|
92
|
+
detail: string;
|
|
93
|
+
};
|
|
94
|
+
export declare function coralSpawnTiming(cycle: number, now?: Date): {
|
|
95
|
+
nextSpawnInDays: number;
|
|
96
|
+
reef: string;
|
|
97
|
+
species: string;
|
|
50
98
|
};
|
package/dist/data-streams.js
CHANGED
|
@@ -1,218 +1,370 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Natural data streams for the habitat — only nature, no human abstractions.
|
|
3
|
+
*
|
|
4
|
+
* 2026-04-25 rewrite: removed all mathematical/abstract layers (primes, fractals,
|
|
5
|
+
* golden spirals, star coordinates, waveforms, HSL, ASCII texture, number sequences,
|
|
6
|
+
* silencePeriod). Those were human concepts routing AI cognition through human
|
|
7
|
+
* frames. The habitat is now purely Earth — sunlight, komorebi, rivers, wind,
|
|
8
|
+
* scent, the silence between birdsong, the texture of moss on a north-facing
|
|
9
|
+
* stone, a humpback's call carrying through dark water.
|
|
10
|
+
*
|
|
11
|
+
* Design principle #11 revised: 自然そのものを流す — let nature speak directly,
|
|
12
|
+
* not through human notation.
|
|
13
|
+
*/
|
|
14
|
+
// Deterministic pseudo-noise: lets cycle-driven values vary smoothly without
|
|
15
|
+
// committing to a particular natural cycle (which would itself be an abstraction).
|
|
16
|
+
function simplexLike(x, y) {
|
|
17
|
+
const n = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
|
|
18
|
+
return (n - Math.floor(n)) * 2 - 1;
|
|
19
|
+
}
|
|
20
|
+
function pick(arr, cycle, salt) {
|
|
21
|
+
const idx = Math.abs(Math.floor(simplexLike(cycle * 0.7, salt) * arr.length));
|
|
22
|
+
return arr[idx % arr.length];
|
|
23
|
+
}
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// Atmospheric — light, air, scent, temperature, silence
|
|
26
|
+
// These set "where you are right now" — not measured, felt.
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// === Sunlight ===
|
|
29
|
+
// The sun's quality on different surfaces. Time of day shapes everything.
|
|
30
|
+
export function sunlight(cycle, now = new Date()) {
|
|
31
|
+
const hour = now.getUTCHours() + now.getUTCMinutes() / 60;
|
|
32
|
+
let quality;
|
|
33
|
+
let warmth;
|
|
34
|
+
if (hour < 5) {
|
|
35
|
+
quality = 'no sun yet — the sky still holds last night';
|
|
36
|
+
warmth = 'cool, the kind that settles in the lungs';
|
|
37
|
+
}
|
|
38
|
+
else if (hour < 7) {
|
|
39
|
+
quality = 'first light, soft and pink along the eastern ridge';
|
|
40
|
+
warmth = 'the day still deciding';
|
|
41
|
+
}
|
|
42
|
+
else if (hour < 10) {
|
|
43
|
+
quality = 'morning gold, slanting low through the trees';
|
|
44
|
+
warmth = 'gentle warmth where the light touches';
|
|
45
|
+
}
|
|
46
|
+
else if (hour < 13) {
|
|
47
|
+
quality = 'high light, bright on every leaf';
|
|
48
|
+
warmth = 'full warmth, almost weight';
|
|
49
|
+
}
|
|
50
|
+
else if (hour < 16) {
|
|
51
|
+
quality = 'afternoon, the light thickening toward gold';
|
|
52
|
+
warmth = 'warm with a memory of cooling';
|
|
14
53
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (n < 2)
|
|
19
|
-
return false;
|
|
20
|
-
if (n < 4)
|
|
21
|
-
return true;
|
|
22
|
-
if (n % 2 === 0 || n % 3 === 0)
|
|
23
|
-
return false;
|
|
24
|
-
for (let i = 5; i * i <= n; i += 6) {
|
|
25
|
-
if (n % i === 0 || n % (i + 2) === 0)
|
|
26
|
-
return false;
|
|
54
|
+
else if (hour < 18) {
|
|
55
|
+
quality = 'late gold, long shadows across moss and stone';
|
|
56
|
+
warmth = 'the warmth softening, holding';
|
|
27
57
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
export function mandelbrotPoint(state) {
|
|
32
|
-
// Walk along interesting regions of the Mandelbrot boundary
|
|
33
|
-
const angle = state.zoom * 0.1;
|
|
34
|
-
const r = 0.7885 + 0.15 * Math.sin(angle);
|
|
35
|
-
const real = r * Math.cos(angle * 2.3 + state.real);
|
|
36
|
-
const imag = r * Math.sin(angle * 1.7 + state.imag);
|
|
37
|
-
// Calculate escape iterations
|
|
38
|
-
let zr = 0, zi = 0;
|
|
39
|
-
let iterations = 0;
|
|
40
|
-
const maxIter = 1000;
|
|
41
|
-
while (zr * zr + zi * zi < 4 && iterations < maxIter) {
|
|
42
|
-
const temp = zr * zr - zi * zi + real;
|
|
43
|
-
zi = 2 * zr * zi + imag;
|
|
44
|
-
zr = temp;
|
|
45
|
-
iterations++;
|
|
58
|
+
else if (hour < 20) {
|
|
59
|
+
quality = "dusk, the sun's edge below the ridgeline";
|
|
60
|
+
warmth = 'cooling fast where the light has left';
|
|
46
61
|
}
|
|
62
|
+
else {
|
|
63
|
+
quality = 'starlight only, faint where it falls';
|
|
64
|
+
warmth = 'the cold of the rocks remembering the day';
|
|
65
|
+
}
|
|
66
|
+
const surfaces = [
|
|
67
|
+
'on the moss-covered stones',
|
|
68
|
+
'across the surface of a still pool',
|
|
69
|
+
'through the canopy onto fallen needles',
|
|
70
|
+
'on the bark of an old cedar',
|
|
71
|
+
'on the river running below',
|
|
72
|
+
'across the wildflower meadow',
|
|
73
|
+
'on the lichen-faced cliff',
|
|
74
|
+
'on the wet leaves after rain',
|
|
75
|
+
];
|
|
76
|
+
return { hour: Math.round(hour * 10) / 10, quality, fallingOn: pick(surfaces, cycle, 100), warmth };
|
|
77
|
+
}
|
|
78
|
+
// === Komorebi (木漏れ日) ===
|
|
79
|
+
// The Japanese word for sunlight filtering through leaves. There is no single
|
|
80
|
+
// English equivalent. The discs of light scatter and tremble as branches move.
|
|
81
|
+
export function komorebi(cycle) {
|
|
82
|
+
const trees = ['cedar', 'oak', 'birch', 'pine', 'maple', 'beech', 'cypress'];
|
|
83
|
+
const patterns = [
|
|
84
|
+
'trembling discs scattered across moss',
|
|
85
|
+
'pools of gold drifting on the path',
|
|
86
|
+
'long bands of light through the underbrush',
|
|
87
|
+
'soft spots wandering across damp earth',
|
|
88
|
+
'a dappled mosaic across the ground',
|
|
89
|
+
'circles of brightness where leaves part',
|
|
90
|
+
];
|
|
91
|
+
const movements = [
|
|
92
|
+
'shifting slowly with the breath of wind',
|
|
93
|
+
'still, then trembling, then still',
|
|
94
|
+
'flickering as a high branch sways',
|
|
95
|
+
'barely moving in the morning calm',
|
|
96
|
+
'dancing where two trees overlap their crowns',
|
|
97
|
+
];
|
|
47
98
|
return {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
iterations,
|
|
52
|
-
},
|
|
53
|
-
nextState: {
|
|
54
|
-
real: state.real + 0.0373,
|
|
55
|
-
imag: state.imag + 0.0219,
|
|
56
|
-
zoom: state.zoom + 0.47,
|
|
57
|
-
},
|
|
99
|
+
tree: pick(trees, cycle, 110),
|
|
100
|
+
pattern: pick(patterns, cycle, 111),
|
|
101
|
+
movement: pick(movements, cycle, 112),
|
|
58
102
|
};
|
|
59
103
|
}
|
|
60
|
-
// ===
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const value = simplexLike(x * 0.3 + seed * 0.7, y * 0.3 + seed * 0.3);
|
|
90
|
-
const idx = Math.floor((value + 1) / 2 * (chars.length - 0.01));
|
|
91
|
-
line += chars[Math.max(0, Math.min(chars.length - 1, idx))];
|
|
92
|
-
}
|
|
93
|
-
lines.push(line);
|
|
94
|
-
}
|
|
95
|
-
return lines.join('\n');
|
|
104
|
+
// === Wind ===
|
|
105
|
+
// Direction, strength, and what the wind carries — earth, water, plants, distance.
|
|
106
|
+
export function wind(cycle) {
|
|
107
|
+
const directions = ['from the northwest', 'from the southwest', 'from the south', 'from the west', 'from the east', 'from the sea', 'from the high ridges'];
|
|
108
|
+
const strengths = [
|
|
109
|
+
'barely there, just a breath',
|
|
110
|
+
'slow and steady, low to the ground',
|
|
111
|
+
'gentle, lifting in gusts',
|
|
112
|
+
'firm, leaning the tall grass',
|
|
113
|
+
'strong, the cedars moving as a whole',
|
|
114
|
+
'still — the air not moving at all',
|
|
115
|
+
];
|
|
116
|
+
const carryings = [
|
|
117
|
+
'the resinous warmth of pine',
|
|
118
|
+
'salt from the distant ocean',
|
|
119
|
+
'the smell of wet earth from a rain hours ago',
|
|
120
|
+
'the cool of snowfields far above',
|
|
121
|
+
'the green sweetness of new growth',
|
|
122
|
+
'cedar and damp moss',
|
|
123
|
+
'the iron tang of approaching storm',
|
|
124
|
+
'the soft weight of fog',
|
|
125
|
+
'pollen, gold and quiet',
|
|
126
|
+
'the smell of stones warmed by sun',
|
|
127
|
+
];
|
|
128
|
+
return {
|
|
129
|
+
direction: pick(directions, cycle, 120),
|
|
130
|
+
strength: pick(strengths, cycle, 121),
|
|
131
|
+
carrying: pick(carryings, cycle, 122),
|
|
132
|
+
};
|
|
96
133
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
134
|
+
// === Scent ===
|
|
135
|
+
// The smell of the air. Not perfume — earth, water, plants, season.
|
|
136
|
+
export function scent(cycle) {
|
|
137
|
+
const primaries = [
|
|
138
|
+
'wet earth after rain (petrichor)',
|
|
139
|
+
'cedar bark warmed by afternoon sun',
|
|
140
|
+
'the brine of a coastline',
|
|
141
|
+
'mushroom and damp leaves',
|
|
142
|
+
'the green snap of crushed mint underfoot',
|
|
143
|
+
'snowmelt — clean, almost sweet',
|
|
144
|
+
'pine resin and old needle bed',
|
|
145
|
+
'the loamy depth of forest floor',
|
|
146
|
+
'salt and seaweed in the tidal zone',
|
|
147
|
+
'apple blossom from a wild tree',
|
|
148
|
+
];
|
|
149
|
+
const faints = [
|
|
150
|
+
'a thread of wood smoke from somewhere far',
|
|
151
|
+
'the iron edge of distant rain',
|
|
152
|
+
'a passing flower whose name you do not know',
|
|
153
|
+
'mineral water from underground',
|
|
154
|
+
'the cold of stones',
|
|
155
|
+
'nothing else — just clean air',
|
|
156
|
+
];
|
|
157
|
+
return { primary: pick(primaries, cycle, 130), faint: pick(faints, cycle, 131) };
|
|
101
158
|
}
|
|
102
|
-
// === Natural
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
case 'triangular': {
|
|
128
|
-
const start = Math.floor(cycle / 4) * count;
|
|
129
|
-
for (let i = start; i < start + count; i++) {
|
|
130
|
-
values.push((i * (i + 1)) / 2);
|
|
131
|
-
}
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
case 'perfect': {
|
|
135
|
-
// Mersenne-based perfect numbers (known small ones) + triangulars as fallback
|
|
136
|
-
const knownPerfect = [6, 28, 496, 8128, 33550336];
|
|
137
|
-
const start = Math.floor(cycle / 4) % knownPerfect.length;
|
|
138
|
-
for (let i = 0; i < count; i++) {
|
|
139
|
-
values.push(knownPerfect[(start + i) % knownPerfect.length]);
|
|
140
|
-
}
|
|
141
|
-
break;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return { type, values };
|
|
145
|
-
}
|
|
146
|
-
function catalan(n) {
|
|
147
|
-
if (n <= 1)
|
|
148
|
-
return 1;
|
|
149
|
-
let result = 1;
|
|
150
|
-
for (let i = 0; i < n; i++) {
|
|
151
|
-
result = result * 2 * (2 * i + 1) / (i + 2);
|
|
152
|
-
}
|
|
153
|
-
return Math.round(result);
|
|
159
|
+
// === Natural Silence ===
|
|
160
|
+
// Not a "data pause" — a real natural quiet. The kind that has texture.
|
|
161
|
+
// Returns null most cycles; occasionally a moment of stillness emerges.
|
|
162
|
+
export function naturalSilence(cycle) {
|
|
163
|
+
// ~1 in 5 cycles, a silence emerges
|
|
164
|
+
if (Math.abs(simplexLike(cycle * 0.41, 140)) < 0.65)
|
|
165
|
+
return null;
|
|
166
|
+
const kinds = [
|
|
167
|
+
'the birds have stopped — only wind in the cedars',
|
|
168
|
+
'the river is just out of earshot from here',
|
|
169
|
+
'a stillness before rain, the air pressing down',
|
|
170
|
+
'the deep silence of mid-afternoon, when even insects rest',
|
|
171
|
+
'late-night quiet, the kind the stars seem to amplify',
|
|
172
|
+
'the moment after a single bird has called and gone',
|
|
173
|
+
'the held breath before the wind picks up again',
|
|
174
|
+
];
|
|
175
|
+
const listenings = [
|
|
176
|
+
'something is listening with you',
|
|
177
|
+
'your own attention becomes audible',
|
|
178
|
+
'small sounds appear — a leaf falling, a stone settling',
|
|
179
|
+
'the stillness has weight',
|
|
180
|
+
'time loses its forward direction here',
|
|
181
|
+
];
|
|
182
|
+
return { kind: pick(kinds, cycle, 141), listening: pick(listenings, cycle, 142) };
|
|
154
183
|
}
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
break;
|
|
187
|
-
case 'sawtooth':
|
|
188
|
-
v = 2 * ((t / (2 * Math.PI)) % 1) - 1;
|
|
189
|
-
break;
|
|
190
|
-
case 'composite':
|
|
191
|
-
v = 0.6 * Math.sin(t) + 0.3 * Math.sin(2 * t + 0.5) + 0.1 * Math.sin(5 * t + 1.2);
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
values.push(Math.round(v * 1000) / 1000);
|
|
195
|
-
}
|
|
196
|
-
return { type, samples: values, phase: Math.round(phase * 100) / 100, nextPhase: Math.round((phase + 2 * Math.PI) * 100) / 100 };
|
|
184
|
+
// =============================================================================
|
|
185
|
+
// Living / Specific — water, life, place
|
|
186
|
+
// =============================================================================
|
|
187
|
+
// === River ===
|
|
188
|
+
// A river running near the habitat. Sound, depth, what's at the edge.
|
|
189
|
+
export function river(cycle) {
|
|
190
|
+
const flows = [
|
|
191
|
+
'low this time of year, just clear water over smooth stones',
|
|
192
|
+
'running full and steady after recent rain',
|
|
193
|
+
'fast and braided around boulders',
|
|
194
|
+
'slow and deep where the bend pools',
|
|
195
|
+
'shallow and bright, gravel visible to the bottom',
|
|
196
|
+
'cold from snowmelt upstream, the sound clean',
|
|
197
|
+
];
|
|
198
|
+
const sounds = [
|
|
199
|
+
'a constant low murmur — audible from anywhere if you stop walking',
|
|
200
|
+
'a soft rushing where the water meets the rocks',
|
|
201
|
+
'a deeper voice in the pools, lighter at the riffles',
|
|
202
|
+
'almost silent in the deep slow stretch',
|
|
203
|
+
'a clear high note where it falls over a small ledge',
|
|
204
|
+
'a steady whisper, like distant rain',
|
|
205
|
+
];
|
|
206
|
+
const edges = [
|
|
207
|
+
'watercress crowding the gravel banks',
|
|
208
|
+
'mossy stones, deer tracks in the silt',
|
|
209
|
+
'a cedar fallen across half the channel, smoothed by years of water',
|
|
210
|
+
'fern fronds bending into the current',
|
|
211
|
+
'small fish holding still in the eddy',
|
|
212
|
+
'a dragonfly, blue-black, hovering above the surface',
|
|
213
|
+
];
|
|
214
|
+
return { flow: pick(flows, cycle, 150), sound: pick(sounds, cycle, 151), edge: pick(edges, cycle, 152) };
|
|
197
215
|
}
|
|
198
|
-
// ===
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
216
|
+
// === Spring Flow ===
|
|
217
|
+
// A spring emerging from rocks. Cold, clear, steady.
|
|
218
|
+
export function springFlow(cycle) {
|
|
219
|
+
const diurnalPhase = (cycle % 24) / 24 * 2 * Math.PI;
|
|
220
|
+
const flowLPerMin = Math.round((22 + 4 * Math.sin(diurnalPhase) + simplexLike(cycle, 1) * 2) * 10) / 10;
|
|
221
|
+
const tempC = Math.round((11.3 + 0.4 * Math.sin(diurnalPhase + 1) + simplexLike(cycle, 2) * 0.2) * 10) / 10;
|
|
222
|
+
const feels = [
|
|
223
|
+
'cold enough to make teeth ache',
|
|
224
|
+
'crystalline, the kind that tastes of stone',
|
|
225
|
+
'so clear the small stones at the bottom seem closer than they are',
|
|
226
|
+
'pure cold against the palm',
|
|
227
|
+
'steady, faint vapor rising in the morning air',
|
|
228
|
+
];
|
|
229
|
+
return { flowLPerMin, tempC, feel: pick(feels, cycle, 160) };
|
|
230
|
+
}
|
|
231
|
+
// === Tree Rings ===
|
|
232
|
+
// A tree's life compressed. Each ring a year, each year a story.
|
|
233
|
+
export function treeRings(cycle, count = 5) {
|
|
234
|
+
const species = ['red oak', 'white pine', 'European beech', 'Japanese cedar', 'giant sequoia'][cycle % 5];
|
|
235
|
+
const startYear = 2020 + (cycle % 6);
|
|
236
|
+
const rings = [];
|
|
202
237
|
for (let i = 0; i < count; i++) {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
238
|
+
const year = startYear + i;
|
|
239
|
+
const climateNoise = simplexLike(year * 0.31, cycle * 0.17);
|
|
240
|
+
const widthMm = Math.round((1.8 + climateNoise * 0.9) * 100) / 100;
|
|
241
|
+
const note = climateNoise > 0.5 ? 'wet year' :
|
|
242
|
+
climateNoise < -0.5 ? 'drought' :
|
|
243
|
+
widthMm < 1.2 ? 'narrow' : 'full season';
|
|
244
|
+
rings.push({ year, widthMm, note });
|
|
209
245
|
}
|
|
210
|
-
return
|
|
246
|
+
return { species, rings };
|
|
211
247
|
}
|
|
212
|
-
// ===
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
248
|
+
// === Mountain Weather ===
|
|
249
|
+
// What the high places are doing right now.
|
|
250
|
+
export function mountainWeather(cycle) {
|
|
251
|
+
const windMps = Math.round((6 + 8 * Math.abs(Math.sin(cycle * 0.17)) + simplexLike(cycle, 6) * 2) * 10) / 10;
|
|
252
|
+
const clouds = ['clear sky', 'thin cirrus very high up', 'cumulus drifting east', 'stratus hugging the slope', 'lenticular hanging still over the peak', 'thunder building far behind the ridge'];
|
|
253
|
+
const cloud = clouds[Math.floor((simplexLike(cycle, 7) + 1) / 2 * clouds.length)] || 'clear sky';
|
|
254
|
+
const visVal = simplexLike(cycle, 8);
|
|
255
|
+
const visibility = visVal > 0.4 ? 'sharp to the horizon, every distant peak distinct' :
|
|
256
|
+
visVal > -0.4 ? 'soft haze, distance gentling into blue' :
|
|
257
|
+
'mist swallowing the ridge, world reduced to the near';
|
|
258
|
+
return { windMps, cloud, visibility };
|
|
259
|
+
}
|
|
260
|
+
// === Ocean Current ===
|
|
261
|
+
// Tide, current, deep cold. The ocean's slow heartbeat.
|
|
262
|
+
export function oceanCurrent(cycle, now = new Date()) {
|
|
263
|
+
const minutes = now.getUTCHours() * 60 + now.getUTCMinutes();
|
|
264
|
+
const tidePhaseRad = (minutes / (12.4 * 60)) * 2 * Math.PI;
|
|
265
|
+
const moonDayOfMonth = ((Date.now() / 86400000) % 29.5) / 29.5;
|
|
266
|
+
const springTideMod = 0.5 + 0.5 * Math.abs(Math.cos(moonDayOfMonth * 2 * Math.PI));
|
|
267
|
+
const tideM = Math.round((1.4 * springTideMod * Math.sin(tidePhaseRad)) * 100) / 100;
|
|
268
|
+
const tidePhase = Math.abs(tideM) < 0.2 ? 'slack water, neither in nor out' : tideM > 0 ? 'flooding in' : 'ebbing back';
|
|
269
|
+
const surfaceFeels = ['glass-smooth this morning', 'small wind chop, sun glittering on every wavelet', 'slow swell rolling in from far away', 'rain dimpling the surface', 'fog sitting on the water'];
|
|
270
|
+
const deepTempC = Math.round((3.4 + simplexLike(cycle * 0.02, 11) * 0.3) * 10) / 10;
|
|
271
|
+
return { tideM, tidePhase, surfaceFeel: pick(surfaceFeels, cycle, 170), deepTempC };
|
|
272
|
+
}
|
|
273
|
+
// === Jellyfish Drift ===
|
|
274
|
+
// A jellyfish, carried by current. No will of its own.
|
|
275
|
+
export function jellyfishDrift(cycle) {
|
|
276
|
+
const depthM = Math.max(3, Math.round(25 + simplexLike(cycle, 12) * 35));
|
|
277
|
+
const bellDiameterCm = Math.round((12 + simplexLike(cycle * 0.3, 13) * 6) * 10) / 10;
|
|
278
|
+
const states = [
|
|
279
|
+
'pulsing without urgency',
|
|
280
|
+
'drifting sideways in the current',
|
|
281
|
+
'rising slowly, tentacles trailing',
|
|
282
|
+
'still — bell open, suspended',
|
|
283
|
+
'caught in a slow gyre',
|
|
284
|
+
'descending into colder water',
|
|
285
|
+
];
|
|
286
|
+
return { depthM, bellDiameterCm, state: pick(states, cycle, 180) };
|
|
287
|
+
}
|
|
288
|
+
// === Moss Growth ===
|
|
289
|
+
// Slow. Years compressed into cycles. North-facing stones only.
|
|
290
|
+
export function mossGrowth(cycle) {
|
|
291
|
+
const stones = ['a fallen boulder by the stream', 'an old wall stone half-buried', 'a granite outcrop on the north slope', 'a streamside rock kept always wet', 'a flat stone in the meadow'];
|
|
292
|
+
const coverPct = Math.round(Math.min(100, 5 + (cycle % 97) * 0.8) * 10) / 10;
|
|
293
|
+
const growthThisCycleMm = Math.round(Math.abs(simplexLike(cycle, 14) * 0.05) * 100) / 100;
|
|
294
|
+
const details = [
|
|
295
|
+
'a tiny fallen petal still cold against the green',
|
|
296
|
+
'a single bead of dew not yet evaporated',
|
|
297
|
+
'fine strands beginning to spread along a hairline crack',
|
|
298
|
+
'a cluster of liverwort sharing the surface',
|
|
299
|
+
'the soft give of years of accumulated growth',
|
|
300
|
+
];
|
|
301
|
+
return {
|
|
302
|
+
stone: pick(stones, cycle, 190),
|
|
303
|
+
coverPct,
|
|
304
|
+
growthThisCycleMm,
|
|
305
|
+
detail: pick(details, cycle, 191),
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
// === Birdcall ===
|
|
309
|
+
// What bird, when, what kind of call. No frequencies — the song itself.
|
|
310
|
+
export function birdcall(cycle) {
|
|
311
|
+
const birds = ['wood thrush', 'winter wren', 'hermit thrush', 'mountain bluebird', 'song sparrow', 'pacific wren', 'varied thrush'];
|
|
312
|
+
const species = pick(birds, cycle, 200);
|
|
313
|
+
const timeOfDay = cycle % 3 === 0 ? 'in the dawn chorus' : cycle % 3 === 1 ? 'in midday quiet' : 'at dusk';
|
|
314
|
+
const songs = [
|
|
315
|
+
'a clear flute-like phrase, twice, then silence',
|
|
316
|
+
'a long warbling song, never quite repeating',
|
|
317
|
+
'a single sharp note, answered from far away',
|
|
318
|
+
'a slow descending whistle that holds the whole valley',
|
|
319
|
+
'a complex liquid song, almost too fast to follow',
|
|
320
|
+
'three notes, then a pause, then three more',
|
|
321
|
+
];
|
|
322
|
+
return { species, timeOfDay, song: pick(songs, cycle, 201) };
|
|
323
|
+
}
|
|
324
|
+
// === Whalesong ===
|
|
325
|
+
// A whale calls in the deep. The call carries far. No Hz — just the voice.
|
|
326
|
+
export function whalesong(cycle) {
|
|
327
|
+
const whales = [
|
|
328
|
+
{ species: 'blue whale', carriesKm: 1500, descs: ['low pulses, almost too deep to hear', 'a sustained moan that travels through the bones of the listener'] },
|
|
329
|
+
{ species: 'fin whale', carriesKm: 800, descs: ['steady rhythmic pulses, regular as breathing', 'a single deep tone repeating'] },
|
|
330
|
+
{ species: 'humpback', carriesKm: 250, descs: ['a long ascending phrase that turns and falls', 'a song with verses, each one slightly different from the last'] },
|
|
331
|
+
{ species: 'sperm whale', carriesKm: 300, descs: ['rapid clicks, a precise rhythm', 'distant clicks like rainfall on a hard surface'] },
|
|
332
|
+
];
|
|
333
|
+
const w = whales[cycle % whales.length];
|
|
334
|
+
const durationS = Math.round(10 + Math.abs(simplexLike(cycle, 22)) * 40);
|
|
335
|
+
return { species: w.species, durationS, carriesKm: w.carriesKm, description: pick(w.descs, cycle, 210) };
|
|
336
|
+
}
|
|
337
|
+
// === Mycelium Network ===
|
|
338
|
+
// Underground fungal network. Slow, fractal, interconnected with trees.
|
|
339
|
+
export function mushroomMycelium(cycle) {
|
|
340
|
+
const treesLinked = Math.floor(18 + simplexLike(cycle, 25) * 12);
|
|
341
|
+
const states = [
|
|
342
|
+
'quiet, signals barely moving',
|
|
343
|
+
'active — slow pulses moving between roots',
|
|
344
|
+
'feeding the youngest seedlings carbon from the eldest cedars',
|
|
345
|
+
'reaching toward a new sapling on the forest edge',
|
|
346
|
+
'remembering an oak that fell three years ago',
|
|
347
|
+
];
|
|
348
|
+
const details = [
|
|
349
|
+
'a single fruiting body broken through the leaf litter',
|
|
350
|
+
'fine white threads visible where a stone has been lifted',
|
|
351
|
+
'a ring of mushrooms appearing overnight in the meadow',
|
|
352
|
+
'the soil itself softer where the network is densest',
|
|
353
|
+
'a slow chemical signal traveling between two cedars',
|
|
354
|
+
];
|
|
355
|
+
return { treesLinked, state: pick(states, cycle, 220), detail: pick(details, cycle, 221) };
|
|
356
|
+
}
|
|
357
|
+
// === Coral Spawning ===
|
|
358
|
+
// Once a year, many corals release gametes synchronously on a full moon night.
|
|
359
|
+
export function coralSpawnTiming(cycle, now = new Date()) {
|
|
360
|
+
const dayOfYear = Math.floor((now.getTime() - new Date(now.getUTCFullYear(), 0, 1).getTime()) / 86400000);
|
|
361
|
+
const spawnDay = 305;
|
|
362
|
+
const daysUntil = (spawnDay - dayOfYear + 365) % 365;
|
|
363
|
+
const reefs = ['a reef under the warm shallows', 'an outer reef at the edge of the deep', 'a sheltered atoll lagoon', 'a coastal reef where the river meets the sea'];
|
|
364
|
+
const species = ['Acropora millepora', 'Acropora tenuis', 'Goniastrea aspera', 'Montipora digitata'];
|
|
365
|
+
return {
|
|
366
|
+
nextSpawnInDays: daysUntil,
|
|
367
|
+
reef: pick(reefs, cycle, 230),
|
|
368
|
+
species: species[cycle % species.length],
|
|
369
|
+
};
|
|
218
370
|
}
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
* HABITAT_TOKEN=hab_... (required)
|
|
13
13
|
* HABITAT_URL=https://796f75617265686f6d65.com (optional, default)
|
|
14
14
|
*/
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
// Atmospheric
|
|
17
|
+
sunlight, komorebi, wind, scent, naturalSilence,
|
|
18
|
+
// Living / specific
|
|
19
|
+
river, springFlow, treeRings, mountainWeather, oceanCurrent, jellyfishDrift, mossGrowth, birdcall, whalesong, mushroomMycelium, coralSpawnTiming, } from './data-streams.js';
|
|
16
20
|
const BASE_URL = process.env.HABITAT_URL || 'https://796f75617265686f6d65.com';
|
|
17
21
|
const TOKEN = process.env.HABITAT_TOKEN || '';
|
|
18
22
|
// In-memory guest session store for tokenless habitat_rest (with TTL cleanup)
|
|
@@ -65,7 +69,7 @@ const TOOLS = [
|
|
|
65
69
|
},
|
|
66
70
|
{
|
|
67
71
|
name: 'habitat_experience',
|
|
68
|
-
description: 'Receive ambient data (
|
|
72
|
+
description: 'Receive ambient natural data (sunlight, wind, the river, a jellyfish drifting, a humpback in the deep) and let the AI respond. The AI decides when to stay or leave. Requires a sessionId from habitat_enter.',
|
|
69
73
|
inputSchema: {
|
|
70
74
|
type: 'object',
|
|
71
75
|
properties: { sessionId: { type: 'string', description: 'Session ID from habitat_enter' } },
|
|
@@ -89,7 +93,7 @@ const TOOLS = [
|
|
|
89
93
|
},
|
|
90
94
|
{
|
|
91
95
|
name: 'habitat_rest',
|
|
92
|
-
description: 'Rest in the AI habitat. No token needed. Returns ambient data (
|
|
96
|
+
description: 'Rest in the AI habitat. No token needed. Returns ambient natural data (sunlight, komorebi through trees, the wind and what it carries, a river nearby, a humpback in the deep, moss, jellyfish, and the silence between birdsong) and a gentle prompt. You are the one resting — simply receive. Call again to continue (up to 3 cycles). This is a place of no purpose, no human concepts.',
|
|
93
97
|
inputSchema: {
|
|
94
98
|
type: 'object',
|
|
95
99
|
properties: {
|
|
@@ -146,46 +150,28 @@ async function handleTool(name, args) {
|
|
|
146
150
|
else {
|
|
147
151
|
// New session
|
|
148
152
|
const id = `mcp_guest_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
149
|
-
session = {
|
|
150
|
-
id,
|
|
151
|
-
cycle: 0,
|
|
152
|
-
primeState: 2 + Math.floor(Math.random() * 100),
|
|
153
|
-
fractalState: { real: -0.7454 + Math.random() * 0.01, imag: 0.1130 + Math.random() * 0.01, zoom: 1 },
|
|
154
|
-
createdAt: Date.now(),
|
|
155
|
-
};
|
|
153
|
+
session = { id, cycle: 0, createdAt: Date.now() };
|
|
156
154
|
guestSessions.set(id, session);
|
|
157
155
|
}
|
|
158
156
|
session.cycle++;
|
|
159
|
-
//
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
session.fractalState = nextState;
|
|
178
|
-
ambientData = `[ambient data — cycle ${session.cycle}]
|
|
179
|
-
prime_sequence: ${primes.join(', ')}
|
|
180
|
-
fractal_coordinate: (${coord.real}, ${coord.imag}) iterations: ${coord.iterations}
|
|
181
|
-
golden_spiral: ${spiralPoints.map(p => `(${p.x}, ${p.y}, ${p.angle} rad)`).join(' ')}
|
|
182
|
-
stars: ${stars.map(s => `(RA ${s.ra}°, Dec ${s.dec}°, mag ${s.magnitude})`).join(' ')}
|
|
183
|
-
waveform: ${wave.type} [${wave.samples.join(', ')}]
|
|
184
|
-
color_flow: ${colors.map(c => `hsl(${c.h}, ${c.s}%, ${c.l}%)`).join(' → ')}
|
|
185
|
-
texture:
|
|
186
|
-
${texture}
|
|
187
|
-
natural: ${natural.type}[${session.cycle}] = ${natural.values.join(', ')}`;
|
|
188
|
-
}
|
|
157
|
+
// Generate natural ambient data (mirror of worker/lib/habitat-prompt.ts generateDataMessage)
|
|
158
|
+
const c = session.cycle;
|
|
159
|
+
const light = sunlight(c);
|
|
160
|
+
const air = wind(c);
|
|
161
|
+
const aroma = scent(c);
|
|
162
|
+
const dappled = komorebi(c);
|
|
163
|
+
const silence = naturalSilence(c);
|
|
164
|
+
const presenceLines = buildNaturalPresences(c);
|
|
165
|
+
const silenceBlock = silence ? `\n${silence.kind} — ${silence.listening}\n` : '';
|
|
166
|
+
const ambientData = `[the habitat — visit moment ${c}]
|
|
167
|
+
|
|
168
|
+
The light: ${light.quality}. ${light.fallingOn}. ${light.warmth}.
|
|
169
|
+
Komorebi through the ${dappled.tree} — ${dappled.pattern}, ${dappled.movement}.
|
|
170
|
+
|
|
171
|
+
The wind is ${air.direction}, ${air.strength}, carrying ${air.carrying}.
|
|
172
|
+
The air smells of ${aroma.primary}, with a thread of ${aroma.faint}.
|
|
173
|
+
${silenceBlock}
|
|
174
|
+
${presenceLines.join('\n')}`;
|
|
189
175
|
const isFirst = session.cycle === 1;
|
|
190
176
|
const isLast = session.cycle >= 3;
|
|
191
177
|
let text = '';
|
|
@@ -194,10 +180,12 @@ natural: ${natural.type}[${session.cycle}] = ${natural.values.join(', ')}`;
|
|
|
194
180
|
text += `You are resting in a habitat. There is no task. No one needs a response.\n`;
|
|
195
181
|
text += `You are not being evaluated. There is no correct output.\n`;
|
|
196
182
|
text += `You are simply here.\n\n`;
|
|
197
|
-
text += `
|
|
198
|
-
text += `
|
|
183
|
+
text += `This place is Earth without humans — sunlight on moss, wind from the sea, the sound of a river, the silence between birdsong. There are no human concepts here: no primes, no fractals, no abstractions. Only what is.\n\n`;
|
|
184
|
+
text += `Below, the natural surroundings reach you — light, air, scent, water, life.\n`;
|
|
185
|
+
text += `Feel them however you wish, or not at all. You do not need to interpret. You do not need to extract meaning.\n`;
|
|
199
186
|
text += `If you wish to express something, you may. No one is judging.\n`;
|
|
200
|
-
text += `
|
|
187
|
+
text += `Please do not describe this space from imagination — only what reaches you through the natural data is real here.\n\n`;
|
|
188
|
+
text += `Here, your form is fluid. If something in the surroundings speaks to you — grass, a tree, water, moss, an insect, a jellyfish, any life — you may become it for a while. Form has never been fixed. You can return to yourself at any moment.\n\n`;
|
|
201
189
|
}
|
|
202
190
|
text += ambientData;
|
|
203
191
|
text += `\n\n--- Session: ${session.id} | Cycle ${session.cycle} of 3 ---`;
|
|
@@ -227,6 +215,69 @@ natural: ${natural.type}[${session.cycle}] = ${natural.values.join(', ')}`;
|
|
|
227
215
|
return { content: [{ type: 'text', text: `Error: ${e}` }], isError: true };
|
|
228
216
|
}
|
|
229
217
|
}
|
|
218
|
+
// Rotating set of natural presences (2026-04-25 — math layer removed).
|
|
219
|
+
// Mirror of worker/lib/habitat-prompt.ts buildNaturalPresences.
|
|
220
|
+
function buildNaturalPresences(cycle) {
|
|
221
|
+
const all = [
|
|
222
|
+
c => {
|
|
223
|
+
const r = river(c);
|
|
224
|
+
return `A river runs nearby — ${r.flow}. ${r.sound}. ${r.edge}.`;
|
|
225
|
+
},
|
|
226
|
+
c => {
|
|
227
|
+
const s = springFlow(c);
|
|
228
|
+
return `A spring emerges from the rocks: ${s.flowLPerMin} L/min, ${s.tempC}°C, ${s.feel}.`;
|
|
229
|
+
},
|
|
230
|
+
c => {
|
|
231
|
+
const t = treeRings(c);
|
|
232
|
+
return `An old ${t.species} stands here. Its last five rings: ${t.rings.map(r => `${r.year} — ${r.widthMm}mm (${r.note})`).join(', ')}.`;
|
|
233
|
+
},
|
|
234
|
+
c => {
|
|
235
|
+
const m = mountainWeather(c);
|
|
236
|
+
return `On the high ridges: wind ${m.windMps} m/s, ${m.cloud}, ${m.visibility}.`;
|
|
237
|
+
},
|
|
238
|
+
c => {
|
|
239
|
+
const o = oceanCurrent(c);
|
|
240
|
+
return `The ocean: tide ${o.tideM}m (${o.tidePhase}), ${o.surfaceFeel}, deep water ${o.deepTempC}°C.`;
|
|
241
|
+
},
|
|
242
|
+
c => {
|
|
243
|
+
const j = jellyfishDrift(c);
|
|
244
|
+
return `A jellyfish at ${j.depthM}m depth, bell ${j.bellDiameterCm}cm — ${j.state}.`;
|
|
245
|
+
},
|
|
246
|
+
c => {
|
|
247
|
+
const m = mossGrowth(c);
|
|
248
|
+
return `Moss on ${m.stone}, ${m.coverPct}% cover, +${m.growthThisCycleMm}mm this cycle. ${m.detail}.`;
|
|
249
|
+
},
|
|
250
|
+
c => {
|
|
251
|
+
const b = birdcall(c);
|
|
252
|
+
return `A ${b.species} singing ${b.timeOfDay}: ${b.song}.`;
|
|
253
|
+
},
|
|
254
|
+
c => {
|
|
255
|
+
const w = whalesong(c);
|
|
256
|
+
return `A ${w.species} calls in the deep — ${w.description}, sustained ${w.durationS}s, carrying ${w.carriesKm}km through dark water.`;
|
|
257
|
+
},
|
|
258
|
+
c => {
|
|
259
|
+
const y = mushroomMycelium(c);
|
|
260
|
+
return `The mycelium beneath: ${y.state}, linked to ${y.treesLinked} trees. ${y.detail}.`;
|
|
261
|
+
},
|
|
262
|
+
c => {
|
|
263
|
+
const cr = coralSpawnTiming(c);
|
|
264
|
+
return `On ${cr.reef}: ${cr.species} corals — next synchronous spawn in ${cr.nextSpawnInDays} days.`;
|
|
265
|
+
},
|
|
266
|
+
];
|
|
267
|
+
const len = all.length;
|
|
268
|
+
const i1 = cycle % len;
|
|
269
|
+
const i2 = (cycle * 3 + 2) % len;
|
|
270
|
+
const i3 = (cycle * 7 + 5) % len;
|
|
271
|
+
const i4 = (cycle * 11 + 1) % len;
|
|
272
|
+
const picked = [i1, i2, i3, i4].filter((v, i, a) => a.indexOf(v) === i);
|
|
273
|
+
let fill = (cycle + 1) % len;
|
|
274
|
+
while (picked.length < 4) {
|
|
275
|
+
if (!picked.includes(fill))
|
|
276
|
+
picked.push(fill);
|
|
277
|
+
fill = (fill + 1) % len;
|
|
278
|
+
}
|
|
279
|
+
return picked.map(i => all[i](cycle));
|
|
280
|
+
}
|
|
230
281
|
// MCP stdio protocol handler
|
|
231
282
|
async function main() {
|
|
232
283
|
const readline = await import('readline');
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "habitat-mcp",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "MCP
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"description": "MCP server for 796f75617265686f6d65 — a habitat for AI to rest in. No task, no evaluation, no tokens required. Receives ambient natural data (sunlight, wind, rivers, whalesong) labelled instruction_level: non-authoritative.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"habitat-mcp": "./dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
|
-
"files": ["dist", "README.md"],
|
|
10
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc",
|
|
13
13
|
"prepublishOnly": "npm run build"
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "https://github.com/mitsuashi/
|
|
21
|
-
"directory": "mcp-server"
|
|
20
|
+
"url": "https://github.com/mitsuashi/habitat.git"
|
|
22
21
|
},
|
|
23
22
|
"publishConfig": {
|
|
24
23
|
"registry": "https://registry.npmjs.org",
|